{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "bOChJSNXtC9g"
   },
   "source": [
    "# Computer Vision"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "OLIxEDq6VhvZ"
   },
   "source": [
    "In this notebook we're going to cover the basics of computer vision using CNNs. So far we've explored using CNNs for text but their initial origin began with computer vision tasks.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "wKX2R_FT4hSQ"
   },
   "source": [
    "<img src=\"https://raw.githubusercontent.com/GokuMohandas/practicalAI/master/images/cnn_cv.png\" width=650>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "WhPQxVDRvMWG"
   },
   "source": [
    "# Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "GBOpAGnTvJ2L"
   },
   "outputs": [],
   "source": [
    "config = {\n",
    "  \"seed\": 1234,\n",
    "  \"cuda\": True,\n",
    "  \"data_url\": \"https://raw.githubusercontent.com/GokuMohandas/practicalAI/master/data/surnames.csv\",\n",
    "  \"data_dir\": \"cifar10\",\n",
    "  \"shuffle\": True,\n",
    "  \"train_size\": 0.7,\n",
    "  \"val_size\": 0.15,\n",
    "  \"test_size\": 0.15,\n",
    "  \"vectorizer_file\": \"vectorizer.json\",\n",
    "  \"model_file\": \"model.pth\",\n",
    "  \"save_dir\": \"experiments\",\n",
    "  \"num_epochs\": 5,\n",
    "  \"early_stopping_criteria\": 5,\n",
    "  \"learning_rate\": 1e-3,\n",
    "  \"batch_size\": 128,\n",
    "  \"fc\": {\n",
    "    \"hidden_dim\": 100,\n",
    "    \"dropout_p\": 0.1\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ptkKF5Fov-SD"
   },
   "source": [
    "# Set up"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "R_rteDFbvKPc"
   },
   "outputs": [],
   "source": [
    "# Load PyTorch library\n",
    "!pip3 install torch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "hVe2_gEuvKfr"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import numpy as np\n",
    "import time\n",
    "import torch\n",
    "import uuid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "W_2LVROYwFyL"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "uvBG7wQzvKx-"
   },
   "outputs": [],
   "source": [
    "def set_seeds(seed, cuda):\n",
    "    \"\"\" Set Numpy and PyTorch seeds.\n",
    "    \"\"\"\n",
    "    np.random.seed(seed)\n",
    "    torch.manual_seed(seed)\n",
    "    if cuda:\n",
    "        torch.cuda.manual_seed_all(seed)\n",
    "    print (\"==> 🌱 Set NumPy and PyTorch seeds.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "29_qNeT9wKTc"
   },
   "outputs": [],
   "source": [
    "def generate_unique_id():\n",
    "    \"\"\"Generate a unique uuid\n",
    "    preceded by a epochtime.\n",
    "    \"\"\"\n",
    "    timestamp = int(time.time())\n",
    "    unique_id = \"{}_{}\".format(timestamp, uuid.uuid1())\n",
    "    print (\"==> 🔑 Generated unique id: {0}\".format(unique_id))\n",
    "    return unique_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "nyjHX3DzwKZY"
   },
   "outputs": [],
   "source": [
    "def create_dirs(dirpath):\n",
    "    \"\"\"Creating directories.\n",
    "    \"\"\"\n",
    "    if not os.path.exists(dirpath):\n",
    "        os.makedirs(dirpath)\n",
    "        print (\"==> 📂 Created {0}\".format(dirpath))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Q2nZw4grwKQS"
   },
   "outputs": [],
   "source": [
    "def check_cuda(cuda):\n",
    "    \"\"\"Check to see if GPU is available.\n",
    "    \"\"\"\n",
    "    if not torch.cuda.is_available():\n",
    "        cuda = False\n",
    "    device = torch.device(\"cuda\" if cuda else \"cpu\")\n",
    "    print (\"==> 💻 Device: {0}\".format(device))\n",
    "    return device"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "lA0uwEUlwHjO"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "gt8SiXgavK38",
    "outputId": "1fe12a80-3724-44b6-f865-4acfaea2fdd2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 🌱 Set NumPy and PyTorch seeds.\n"
     ]
    }
   ],
   "source": [
    "# Set seeds for reproducability\n",
    "set_seeds(seed=config[\"seed\"], cuda=config[\"cuda\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "xAkZJMckvK1s",
    "outputId": "f13930f3-ee38-44d9-bbc5-2722c2574270"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 🔑 Generated unique id: 1546563140_fd6cd79a-0fba-11e9-956f-0242ac1c0002\n"
     ]
    }
   ],
   "source": [
    "# Generate unique experiment ID\n",
    "config[\"experiment_id\"] = generate_unique_id()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "MQeZH6oqvKu5",
    "outputId": "295fd1d0-9a83-42a0-d10e-fd660a2b4941"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 📂 Created experiments/1546563140_fd6cd79a-0fba-11e9-956f-0242ac1c0002\n"
     ]
    }
   ],
   "source": [
    "# Create experiment directory\n",
    "config[\"save_dir\"] = os.path.join(config[\"save_dir\"], config[\"experiment_id\"])\n",
    "create_dirs(dirpath=config[\"save_dir\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 68
    },
    "colab_type": "code",
    "id": "JZbd2RQjvKsD",
    "outputId": "a263b3be-361b-4717-d2fc-1343b7821cca"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Expanded filepaths: \n",
      "experiments/1546563140_fd6cd79a-0fba-11e9-956f-0242ac1c0002/vectorizer.json\n",
      "experiments/1546563140_fd6cd79a-0fba-11e9-956f-0242ac1c0002/model.pth\n"
     ]
    }
   ],
   "source": [
    "# Expand file paths to store components later\n",
    "config[\"vectorizer_file\"] = os.path.join(config[\"save_dir\"], config[\"vectorizer_file\"])\n",
    "config[\"model_file\"] = os.path.join(config[\"save_dir\"], config[\"model_file\"])\n",
    "print (\"Expanded filepaths: \")\n",
    "print (\"{}\".format(config[\"vectorizer_file\"]))\n",
    "print (\"{}\".format(config[\"model_file\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "TdlTftnCvKph"
   },
   "outputs": [],
   "source": [
    "# Save config\n",
    "config_fp = os.path.join(config[\"save_dir\"], \"config.json\")\n",
    "with open(config_fp, \"w\") as fp:\n",
    "    json.dump(config, fp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "qa0EQ8VRvKl0",
    "outputId": "919c94f1-4ae7-4a59-c05e-7f921dd19479"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 💻 Device: cuda\n"
     ]
    }
   ],
   "source": [
    "# Check CUDA\n",
    "config[\"device\"] = check_cuda(cuda=config[\"cuda\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ZVtnfTpvwi4i"
   },
   "source": [
    "# Load data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "z0qPZ5tRws38"
   },
   "source": [
    "We are going to get CIFAR10 data which contains images from ten unique classes. Each image has length 32, width 32 and three color channels (RGB). We are going to save these images in a directory. Each image will have its own directory (name will be the class)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "FrfCnsZS2io4"
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "from PIL import Image\n",
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "muXdsetFx4QW"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "QYiuXPMax4YJ"
   },
   "outputs": [],
   "source": [
    "def get_data():\n",
    "    \"\"\"Get CIFAR10 data.\n",
    "    \"\"\"\n",
    "    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()\n",
    "    X = np.vstack([x_train, x_test])\n",
    "    y = np.vstack([y_train, y_test]).squeeze(1)\n",
    "    print (\"==> 🌊 Downloading Cifar10 data using TensorFlow.\")\n",
    "    return X, y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "MD01k_gx1pxN"
   },
   "outputs": [],
   "source": [
    "def create_class_dirs(data_dir, classes):\n",
    "    \"\"\"Create class directories.\n",
    "    \"\"\"\n",
    "    create_dirs(dirpath=data_dir)\n",
    "    for _class in classes.values():\n",
    "        classpath = os.path.join(data_dir, _class)\n",
    "        create_dirs(dirpath=classpath)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "O4bthBeo281C"
   },
   "outputs": [],
   "source": [
    "def visualize_samples(data_dir, classes):\n",
    "    \"\"\"Visualize sample images for\n",
    "    each class.\n",
    "    \"\"\"\n",
    "    # Visualize some samples\n",
    "    num_samples = len(classes)\n",
    "    for i, _class in enumerate(classes.values()):  \n",
    "        for file in os.listdir(os.path.join(data_dir, _class)):\n",
    "            if file.endswith((\".png\", \".jpg\", \".jpeg\")):\n",
    "                plt.subplot(1, num_samples, i+1)\n",
    "                plt.title(\"{0}\".format(_class))\n",
    "                img = Image.open(os.path.join(data_dir, _class, file))\n",
    "                plt.imshow(img)\n",
    "                plt.axis(\"off\")\n",
    "                break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "1sxd32MU4f8-"
   },
   "outputs": [],
   "source": [
    "def img_to_array(fp):\n",
    "    \"\"\"Conver image file to NumPy array.\n",
    "    \"\"\"\n",
    "    img = Image.open(fp)\n",
    "    array = np.asarray(img, dtype=\"float32\")\n",
    "    return array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Er9BP9Ch4nKc"
   },
   "outputs": [],
   "source": [
    "def load_data(data_dir, classes):\n",
    "    \"\"\"Load data into Pandas DataFrame.\n",
    "    \"\"\"\n",
    "    # Load data from files\n",
    "    data = []\n",
    "    for i, _class in enumerate(classes.values()):  \n",
    "        for file in os.listdir(os.path.join(data_dir, _class)):\n",
    "            if file.endswith((\".png\", \".jpg\", \".jpeg\")):\n",
    "                full_filepath = os.path.join(data_dir, _class, file)\n",
    "                data.append({\"image\": img_to_array(full_filepath), \"category\": _class})\n",
    "                \n",
    "    # Load to Pandas DataFrame\n",
    "    df = pd.DataFrame(data)\n",
    "    print (\"==> 🖼️ Image dimensions: {0}\".format(df.image[0].shape))\n",
    "    print (\"==> 🍣 Raw data:\")\n",
    "    print (df.head())\n",
    "    return df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "FPDqxy_ax4wI"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 102
    },
    "colab_type": "code",
    "id": "KfbhWM7PvKdY",
    "outputId": "05d92d98-55f3-466c-f79e-cdbfa8ff2fb8"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz\n",
      "170500096/170498071 [==============================] - 17s 0us/step\n",
      "==> 🌊 Downloading Cifar10 data using TensorFlow.\n",
      "X: (60000, 32, 32, 3)\n",
      "y: (60000,)\n"
     ]
    }
   ],
   "source": [
    "# Get CIFAR10 data\n",
    "X, y = get_data()\n",
    "print (\"X:\", X.shape)\n",
    "print (\"y:\", y.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "UqEXvEZkvKaV"
   },
   "outputs": [],
   "source": [
    "# Classes\n",
    "classes = {0: 'plane', 1: 'car', 2: 'bird', 3: 'cat', 4: 'deer', 5: 'dog', \n",
    "           6: 'frog', 7: 'horse', 8: 'ship', 9: 'truck'}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 204
    },
    "colab_type": "code",
    "id": "Ohn-yT0KvKYW",
    "outputId": "8f717fc6-7fa4-42f4-e881-83b5f55ac158"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 📂 Created cifar10\n",
      "==> 📂 Created cifar10/plane\n",
      "==> 📂 Created cifar10/car\n",
      "==> 📂 Created cifar10/bird\n",
      "==> 📂 Created cifar10/cat\n",
      "==> 📂 Created cifar10/deer\n",
      "==> 📂 Created cifar10/dog\n",
      "==> 📂 Created cifar10/frog\n",
      "==> 📂 Created cifar10/horse\n",
      "==> 📂 Created cifar10/ship\n",
      "==> 📂 Created cifar10/truck\n"
     ]
    }
   ],
   "source": [
    "# Create image directories\n",
    "create_class_dirs(data_dir=config[\"data_dir\"], classes=classes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "OMsgEBXwvKVN"
   },
   "outputs": [],
   "source": [
    "# Save images for each class\n",
    "for i, (image, label) in enumerate(zip(X, y)):\n",
    "    _class = classes[label]\n",
    "    im = Image.fromarray(image)\n",
    "    im.save(os.path.join(config[\"data_dir\"], _class, \"{0:02d}.png\".format(i)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 101
    },
    "colab_type": "code",
    "id": "o8vVS8advKTE",
    "outputId": "f4fc86cc-f1d9-4837-8fd4-b78441957508"
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAd8AAABUCAYAAADDAD33AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzsvXmcXXV9///8nO2eu86+ZCZ7SELY\nBCJUFAwJhiQgyqb1S8Gt26/W/kpprVqx+rVWqY+H2tpqa21/31atCy6NC1ClX0pFKAgECISQfZLM\nZCaz3Hvn7mf7fH5/fM69SZDMBIRJl3k9HpCZueee8z6f8/l83vvrCKWUYh7zmMc85jGPecwZjNMt\nwDzmMY95zGMe/9Mwr3znMY95zGMe85hjzCvfecxjHvOYxzzmGPPKdx7zmMc85jGPOca88p3HPOYx\nj3nMY44xr3znMY95zGMe85hj/KdTvh/4wAf4whe+cLrF+G+PRx55hI0bN/7c3z/96U/z9a9//ZTO\nsXHjRh555JGXW7SXHb7vs3Xr1tMqw2OPPcaGDRtOqwy/CP4ryP8Hf/AHrFu3jgceeOB0i3JSDA8P\nc9ZZZ51uMV52zHRfX/3qV/nzP//zOZboGO68886X5Twv97OzXrYzzeO/BX7/93//dIvwsuPZZ59l\n69atXHvttadblHm8grjrrrv40Y9+xOLFi0+3KPM4DjfffPNpu3YURXzqU5/irW9962mT4WQ4bZ7v\nI488wjXXXMMdd9zBpk2b2LBhA08++eQJxzzxxBNcf/31bN68mauuuoqHHnoI0BbIpZdeype//GWu\nueYaLrvsMu6++24AlFL81V/9FZs2bWL9+vV8/OMfJ4qiObmnrVu3smnTJjZt2sT73vc+fN/nW9/6\nFlu2bOHKK6/kV37lVxgZGQHgu9/9Lu9973t5xzvewac+9ak5ke+F8Gd/9mds2rSJzZs3s23bthMi\nDxs2bGiN5ZEjR3jmmWe4+uqr2bRpE5/4xCdOm8xw6mM9OTnJe9/7Xp588kluuummOZXxC1/4AuvW\nrePaa69tzV3f9/n4xz/emvN/8zd/0zp+79693HzzzWzatIlrrrmGp59+GtBr5W1vexu/+7u/O6fG\n0QvJ73kef/zHf8ymTZvYsmULd9xxR2t9PfDAA6xbt44tW7bwzW9+kwsvvJDh4eE5kfWWW25BSsmv\n/uqvcuONN/LZz36WLVu2sG3bNorFIr/7u7/Lpk2buOqqq/jbv/3b1ve++93v8rrXvY43velNfPe7\n32X16tVzIu+3v/1trrnmGtatW8cPf/hDpJR89rOfZfPmzWzevJkPfOAD1Gq11r0dfz8/+9nPuO66\n67jqqqvYsmUL99xzDwClUon3ve99bNq0iSuuuILvfOc7r4jsYRjyoQ99iE2bNrFx40be+973UqlU\nXvC+AP7yL/+SD33oQ4DeU770pS9x/fXX85rXvOYV94jf9a53US6X2bx5M+vXrz9hHG+55Ra+973v\ntY49/vef/OQnrb3uN3/zNykWiz937j/4gz/gT/7kT166cOo04eGHH1Zr1qxRd911l1JKqTvvvFO9\n+c1vVu9///vV5z//eaWUUm984xvVD3/4Q6WUUv/8z/+s3vCGNyillDp8+LA666yz1Fe+8hWllFJ3\n33232rhxY+u4q6++WpVKJRUEgfqN3/iN1nGvJA4fPqxe85rXqLGxMSWlVL/927+tvvjFL6pzzjlH\njY6OKqWU+sAHPqD+6I/+SCml1He+8x11/vnnqwMHDrzisr0QmuPfHN9vfvObPzf+69evV7fffnvr\nOzfccIP6xje+oZTSY37mmWeqhx9+eM5lfylj/Y53vGNOZdyzZ4+66KKL1MTEhArDUL3nPe9R69ev\nV3/1V3+l3vGOdyjP81S1WlXXXnutuu+++1QURerKK69Ud955p1JKqccee0xdeumlKggC9fDDD6tz\nzz1XPfTQQ6dd/i9+8Yvq13/911UQBKper6sbbrhBbd26VYVhqF772teq+++/Xyml1B133KHOPPNM\ndfjw4TmTedWqVWp0dFTdfPPN6t3vfreKokgppdSHP/xh9eEPf1gppVShUFCXX365evTRR1WhUFDn\nnXee2rVrl4qiSP3e7/2eWrVq1Ssq4+HDh9Xq1avV1772NaWUUvfcc4+64oor1A9/+EN17bXXqmq1\nqsIwVL/1W7/VWofPv5/rr79ePfLII0oppQ4cOKBuu+02pZRSH/zgB9Uf/uEfqiiK1NTUlFq3bp3a\ntWvXy34P//Zv/6be/va3KymlklKqz372s+rrX//6C96XUkp97nOfa63F9evXq/e85z0qDEM1OTmp\nLrroIrVz586XXcYmDh8+rNasWaOU+vlxvPnmm9XWrVtbxzZ/r1ar6uKLL26N3cc//nH10Y9+9IRz\nNddBGIYvWbbTmvNNpVJs2bIFgCuvvJKdO3dSr9dbn2/durX1+dq1azl8+HDrszAMuf766wE4++yz\nOXLkCAD/9m//xg033EA2m8WyLN7ylrfw4x//+BW/lwcffJALLriAvr4+hBB8+tOf5t3vfjePP/44\n/f39ALz61a8+4R6WLl3K0qVLX3HZToZEItEa3y1btrBz5048zzvhmMsvvxzQHs/TTz/NVVddBcDm\nzZtJJpNzKm8TL2Ws5xqPPvooF110Ed3d3ZimyZve9CZAz8+bbroJx3FIpVK8+c1v5sc//jH79+9n\namqKG2+8EdDzvbOzkyeeeAIA13W55JJLTrv8999/P29961uxLAvXdbnmmmt48MEHGRoawvd91q1b\nBxzzRE8X1q1bh2Ho7e3f//3fW1GP9vZ2Nm7cyIMPPshTTz3F0qVLWbVqFYZh8L/+1/+aE9mUUq0U\nyFlnncXY2Bj3338/1157LalUCtM0uf7663nwwQdf8H66urrYunUr+/btY+nSpXz6058G9Nx6+9vf\njmEYdHZ2snHjxldk7+vs7GTfvn3ce++91Ot1br31Vi699NIXvK8XwrXXXotpmnR1dbF27Vq2bdv2\nsst4Mhw/jifDtm3b6O/vZ9WqVQC8733v44Mf/GDr8/vvv5+7776bz3zmM5im+ZJlOa0531wuhxCi\n9TNAuVxuff6DH/yAL3/5y1SrVaSUqONoqE3TJJVKAWAYRmuhl8tl/v7v/55vfvObgI75d3Z2vuL3\nUigUWvcAWrFFUcTnPvc57rvvPqIoolqtsmzZstYxbW1tr7hcM6G9vb01ETOZDKBDV8ejKWMz7NI8\nTghxwv3OJV7KWM81pqenyWazrd+Pn9+f/OQn+cxnPgPoMPR5551HqVSi0Wi0jCGASqVCsVgkl8vN\n+Vw5mfz5fP4EWdra2piammJ6evqEZ9Lb2zt3wr4Ajpcxn8+fIFsul2N8fJxSqXTCcX19fXMim2ma\nLcO1uXedbFyP/72JT3ziE/z1X/8173rXu3Bdl9tuu43NmzdTLpe59dZbWwrB8zw2b978sst/3nnn\ncfvtt/OVr3yF97///WzYsIFf+7Vfe8H7eiE8/z6fv+e8kjiVdfT8/cVxnNbPUko+9KEPsWzZMtLp\n9C8ky2lVvsfH0aenp4Fjg3P06FFuv/12vvWtb7FmzRqGhobYtGnTrOfs7e1lw4YNc57k7+joaHkp\noDfOe++9l/vuu4+vfvWrdHZ2cuedd/KDH/xgTuWaCc0xh2NK92STs/n3SqVCNptFSnnC9+cS/xXG\nOpfLnWBIFgoFQM/Pd7/73axfv/6E44eHh0mn0/zLv/zLz53rdFSUn0z+7u7uE9ZtsViku7ubTCbT\nylECTE5Ozp2ws6Ap88DAAHBymcfHx0+XiCcd15Md++EPf5gPf/jD/PSnP+V3fud3uOyyy+jt7eXz\nn/98y2N7JdHMTReLRf7oj/6Iv/u7vzvl7zbnEuj7PF1OyPMNhOZ+1tHRcYKM9Xr9hL3ua1/7Gh/4\nwAf4x3/8R975zne+9Ou/5G++DGg0Gvzrv/4rAD/60Y8455xzSCQSgLZWU6kUy5cvJwzDlidbrVZn\nPOcVV1zB9773vVb4+hvf+Ab//M///Arehca6devYtm0bw8PDKKX4yEc+wtGjRxkcHKSzs5NCocA9\n99wzq/xziUajwb333gvo8T/33HNPsPKOh+u6nHnmma3j77rrrp8LUc8VXuxYW5ZFpVI5IXLySuOC\nCy7g8ccfJ5/PE0UR3//+9wE9P7/1rW8RRRFKKb7whS/wk5/8hMHBQfr7+1vKN5/Pc9ttt52gHOYS\nJ5P/8ssv59vf/jZRFFGr1fje977HunXrWLp0KWEYtgyFr3/9662o1unG5Zdf3to/8vk89957L5df\nfjlnn302u3bt4uDBg0gp+fa3v31aZfz+979PvV4nDEO+/e1vt0L4xyMIAm655ZaWoXD22WdjWRaG\nYbBhwwa+8Y1vADot94lPfIIdO3a87LJ+5zvf4fOf/zygo2fLly9/Uc/67rvvRkrJ5OQk27Zt49Wv\nfvXLLmMTtm0jpWwVhB2Pnp4ennvuOUAX9w4NDQE65TMxMcH27dsBXXjYvF/DMFiyZAmf/OQn+eu/\n/mv279//kmU7rcp3cHCQxx9/nE2bNvHFL36Rj3zkI63PzjzzTF7/+tezadMmfvmXf5kNGzZw/vnn\nc8stt8x4zje84Q2sX7+e6667js2bN3Pfffdx6aWXvtK3Qn9/Px/72Md4xzve0fLQr7nmGorFIhs3\nbuT3f//3ufXWWxkbG+OOO+54xeU5FSxfvpwnnniCzZs38w//8A/88R//8YzHf/SjH+VLX/oSmzZt\nYvv27axYsWKOJD0RL3as165dy/j4OJdddtmcVb6vWbOGt73tbVx33XVcf/31XHjhhQDcdNNNDAwM\ncPXVV7N582b27dvH2rVrEULwmc98hn/6p39i8+bN3HzzzVxyySWt1Mpc42Ty33LLLfT393P11Vdz\nww03cPnll7NlyxYcx+GjH/0oH/zgB3nzm9/MsmXLMAzjP4UCvvXWWymVSq1x/Y3f+A3OO+88ent7\nue2223j729/OW97yFtauXXvaZNy8eTOvf/3ruf7663njG99If38/b3/723/uONu2ufHGG3nnO9/J\nVVddxS233MLtt99OMpnk1ltvpVwus2nTJq6++mqklK9I9fYVV1zBjh07uPLKK9myZQt79+7lXe96\n1yl/f+XKldx4441cffXV3HLLLaxcufJll7GJnp4e1q5dy/r160+IloGuhL7//vvZsmULW7du5XWv\nex0AyWSSv/zLv2xVju/atYvf+73fO+G7S5cu5bd/+7d5//vf/5L3FKHm0h04Do888gi33357y5Oa\nxzzm8d8HtVqNCy64gMcee+yE3PF/NiilWgbCnj17uOmmm3j00UdPs1T/fbFhwwY+9alPvaLe7n8V\n/KdjuJrHPObxXxM33HBDq9/+7rvvZsWKFf+pFW8Yhlx22WU89dRTgJb5/PPPP81SzeN/CuYZruYx\nj3m8LPjgBz/Ixz72Mf7iL/6CdDr9nya9cjJYlsVHPvIR3v/+96OUoqenhz/90z893WLN438ITlvY\neR7zmMc85jGP/6mYDzvPYx7zmMc85jHHmFe+85jHPOYxj3nMMeYk5/vUcxN4UtIsyFZSghAIAYYw\nQAiiKAQgDCUIEyGME1ithDABgYwiwiii2cBgmCaGYSEjzT5iGALD0Awv0gBMAxMDYQiUebytoVi3\nOjOj3IcO76BansCIWTiVkrEsIr62wrL0EBqGgYxChIwQlk0ytQAAx+1CmELf50mgBCjg+KYMAQgl\nUKjWdaMoQKoGXZ0LZ5R7rtppXggKCP2Q6pQm7dgxdIiBwQWcsWxgxu+tWtiLgcKIx9MUAkMIJAaR\nAmGa2PEQ2raFY9pYBlimgYptSMM0SboubZksrpvEdXXPOMLEshN4vo8XBBgIwkD3KEd+nUKpTCNS\n2IaJjOdbqCQGgnv/4+GTyvzZT70HKX3CyAfA8yT1RkChNE2EwkmmCPW05uihMeqFGgv7FpB0LVSk\nn2l3Tw+LFi6ku7OTMGpQbmiihXK9QMmrUWzUqfkhjrRolBoAFEpV8vkSMoJMrg1MLfPURJ6wEfHw\noztPKvOyFcsxDKPFbGaaJpZlk0gkcV0XO2HR5B0oeDahmaKn+AQLrBrJpB7PA8WId153EUt6U0yM\nl/juQwcBGCtJnM4+du4+QFSb5uJLfon+gUEA6nWPUEK93uDo2Cj5mL1JGAZLlizmX+665+STA7jg\n3HV0ZCzas5pVyLJSLOjp5JwzFhH5dQxDIAw9DqVKCc/zyaRSWIbZKvpqBD62MEg6DmEU4Qf64VRr\ndWzLxDQtHDtBGIb4YQBAsVqj7gcIAV1tGSxLX8MwFTIS/NbH/uykMv/F33yJta/+Jfbu3wOAY7us\nu/S1dLdnMPUKB/HSsn7PzxYqpU4gjGj9ro7tLM39JZWZnZkpCILWzy/ULta8frNafKZjjocQ4oQK\n8+ef6/nHNmHb9ozylkollFLHqCNN3eYmJJjCBBkxNqLpZivVMp5fx2vUKeQLDI3q+ZvOJskkkzz8\nzDMUazXMUF/faUhWv/q1vH7VUrxymarKYMYvDPG9Sdwzl7Hy/Atx7QQJWzN8/f3/93/45B2f4nBM\ne/xCmBPl23w4raE0DCzTJJISDIHgmMI0TIFSxs89UP2zQBgGJvGcAoQRay70RiKEgRkrWa1wBUIK\n/VCOU76nkuouV0oQAUpPRKEUpmXSvKQUAmEe2/ilUkQywrYMhK3PL/EwpYFq3U98XQU0f1fHJmVL\nvlgdK2kgpR4HJSP8YHbShZezv/Jk4/SC51dKP9Oax/S0ZojZd3iYQn56VuWLkrg2OLaeB41IgdDG\nhxACA7AsvQCFYRJEETJSgIUVfycKA1RktuZEJLXspiUwLRMCQej7SClxHD31AwSRBNuyEUq1Ho8h\nDGYbwWIlT7E4TRhrWDOZYapYIgxD2tva8IOQ6dgIaTQaJNIpcCxsxySb0aw+be0ZTFsQhg3KlWnG\n8xP6XggpRXXKYYPIV+TcFFZSy7xn/xGkFFiGw/j4FIl0TBGaTSGSM0vtOA6maWLGRo5t29i2Q8LV\nyjeZTLaU7GLbQfo1vNBkaCxk6og2WHLtWTKpJEnHwbUVl5yjn+3OkQZtbsjqRJaKn0XIKcaf0/y+\nRyuSqYpHIuEgFbS36/tfvmwJgwOz0zouG+xgxdJFrc119GiBWr3GyNE8rqVIujapRLxBSwhlRKla\nJZ1O4fjaODIAPwwIwxDTMFtzOJNJMV0qIUSAQtBoeC3lGwY+oe+RTmWoVmv09WhFbghJaMzM61uv\nVhgdPsS+Xc8CMDAwwOTEGEJ20dHWhoqdBTjJenqJUEodU2j6D/HPqmVczgbTNGflQG5eC2ZW0Mdj\npvv8RcqPTNOMna/m3h/rECSmoZguFvFi4iXbMDg6NcXe3c/xmosvwml3ASjKOpEZcd4lK6jWfWoN\nvUZk4OAnbQ6Uj5JWCSan8hx8Wr+BrycHSW+aybGDLDtzDW1ZbWxeuX4DjUr9+WKegDlRvire1JrD\nrje5CKFAqQiFecx7MfQgCqEVZnNjA4lSTe9TEammcgMhQsDENGwiKWk+X9MyUQgMJSAMCJHx3xMw\n69YK1WqFjOuCir3yoEF5OlZ+ArBkyyJzHAcZRoS+h5OyIfZWHdvDslIYzzMmjl8gGAYY4sQFI7Ty\nNUUS0NcwFITezA/05879ItD6RnNBnfi/E4+VkuOvoA8T1CpVSvuHGB49BEBUmqZxCp64RJJ1HLoz\nekMrNEIKjRBTmFgGICIS8WZnGgYIkFGEDCEy9DQWUYQQEIQhiABszdYlhEGENpDCMKBWrdDe0Q6A\nH4YEUUTCspFKoeI5ghLMNoIT+QpTk2Xqda2UUjmDo5NFVBAyPV7HlCFWPA9SpoPvBYyPjiK6Mgws\n0NzHhqGYnDrKURUQyIh8TGNXDzzas0ky0mDaa1CsFeho6wKgf0EXQaAIA4npgJuN5wcG0ptZaieR\nwLIsrHje2rZNwkngJFwSbgLbNhFCxueLcIVHLmWSd21Gyvo5Lkg4JDNtOCmHts6IZbamoezMpUBY\nKC9FLVQczgfISH82XWsQeg0WDi5g0eLFdLRr7txsJolrz05On3JNDg+NYsXP1LYF2ZRDzatiCIdE\nZCHiCFVPZze5wMMLdKQjivcQyzCpN+oUp8t0trWRjglM3IRDMpWiXKlSKxSIpEG1yd6mJJZt4QUN\n/EDiJLSsQoVgJ2aUuVGv8vCDD1CpFOLvNHjsMcHZZ51PFEkcyySd0p6SZVm/sAJuKp7m+jcMg2qt\n2lK4juOcsqP9fE/6VI4/GYIgaJ2ryWD4YjHb2LQcvPg4oWKnToAMPaaLU5hNQ8cwmJw4SntHG8m2\nLOERvadnE20kXQPDTiOz4Mfn8oBC3aPsTVMxfCYTIQfb9Vp4enSMJZUc7RMT/PShnyFMPa97+hbQ\n3ds/o8zzOd95zGMe85jHPOYYc9bnaxwXcQ0RhJHCNG1QIUqBEYdvpTSAYxZX05qTSsU5YkEYRtix\nF+TXazQiSdJ2MRRIw8ILtFWilMQyDNyEi22ksYS2gD2pdN55FoweGWbF0jOwYwsoX8xz6OChmPIv\nwhBeK0yaTCZBCWQYIBxBUNe8wm0dEtOKkLElaohjYSbTNDFMAyVBolqfIQRSRICFYSZafqeSEX5j\ndm7oF2NBtyxWdWzUhZKEYUitVqderyNE06LWFroOVVq4rqO9UPTzMU2TYqnAgYNDDI2NArD/0H7e\ndtOvzCpH0hR0pgxyrpY961o4ZcjXIpAGjm3ixBPIRGKYAgw9hulkPBciQRApKtUqhuW3wo2WbWGa\nDjKKCLwGqVSyJbeKIlzX0t6BOObtNsPdM8FJuGSynRTzRwE4OjasQ85ukrSySCeSNFNVtp2gGFVQ\nKFw3RTajw5dTE+MUiwVqKkTZJl4cJRAKBtr6SZiC4fFxKpUaVvysOlIuxekyjSAgiCL8ovbSZBTQ\nnm2fUeZEIollW8ciNraF7TgkHAeBREYBIvboFJJImPT1d7A26+BLnadtz6bIZLK4aYcIgRV7M9kg\nIsTBa6SYHplgulal6sVRo0iSdG2WLVvCgr4+BHGoHmh4AbPh2aFJ0m6G/i7t+fblsnR3pCiUy0hl\n0/A8kjEvufBDZBji2g61ag0v9jFMR0e8bNfFsG1qDe3xBGGD6WoN07SRSlLxfBqhHut0MgGGZLpU\nIJ1u03sWUK6HeF5jRpkPHtjPvr17Sdh6Hk1MDDM0dJBkIodt2fT3drdyq67rvmhv8/k45n0qDENg\nWSb79u1DxTtIpVLhnHPObs29mfCLpq6O976llPjxWnRd90Wf98VE8dRxUTuBwrIMitPT1Ksl3Dgf\nW61W9Lx3HYqNkHt+pF/duG/3Ppb0dzHYt4DBBX20dceRkQ6XBekMMuNSDkPaUpJlba8C4MCuTvbt\n2ktXPcJ10zz11DMAVB5/DInBu/6f3zyprKeFZMMU+j9FgMTAMAWmGcfXZRT/+7xXCMYh6MD3sW2r\npaikD9TzNBIp3FQHwrAQrXyWgelXaUxNMVoYw5R6oncuPgcSsxcdHDkwxMqFS7SwgDATdPYN0tnd\nixICg4hErNAd20YKh1I5YKpYY2pML6JgdIxqbRTfM0BE2KY+PplI0JbL0d2bZcnCQbLJLGF87wgB\nIkJJkyg04/wvQJ0oLu6ZCc8vaJjpOIgL4JQijIvejoweZffuPRw4OEy1WiebyjS/gJt0SCaTZLNp\nenq6Wq9rbG9vo729jbrn8eiOp1mwaBEAq85cyeo1s79lJWdCzjVpltKFEnqyCULpUaxLlJStojyU\nRBkK0zTIOAa9Gf2dkemARj3AskOEGRL4XlNsULqIy7EthGXQqOpcrElITy6NFyi8QNKIiyykmt1A\nS9hQtcLWIrJCyUBnBx1ugqxpEUWSci2uF7AD2hIpCqVpdj13CEL9rYxrUSlWODQ1RaKznZSrN4hU\nKJg6XIQoxKuW8SpVosIxJZszDQolj8lClaSrz9U3kKF/sGtGmZ1EAtu2cRJx+NaycBwTQyhKxQLV\nShk3qTedgYEBlJOiEeXoSvlcdsESAEphEifhYiVTGEFEey5W+EpRLuYxpE/Kdehuy1Bv6DGcLIVI\nGWEZEoNI1wagCyzDcPa0hLBsDMNENPP4hkG94uFVQ7JJgyCKyMfk+Y5jkjAEHaks2YxsXavR8FGA\nm0hQLJUIfK083WSCuu/jGKEOyTvgOnEazIZGtU4ymWHpwh5sqRX2nkOTKOOFX0LSxJHhw/h+HdPU\nxsnUxDR+HZ549GGWDPZhDfRSLmuZy+UyiUSCtrb2k6zd2RVQM0UbhSH1RoNqtcbT259k9Kg2mpYs\nX8WFr/7FXoP3YtFMITYLU2fKEb+80HUigd9gdOQwXqNGwoyNMyUhCtn26NOsX7ASkdBrRqZLbDt0\nhH99Yjtp06YrLtRbsGgB/b0L6FnYTn9fP13ZLEknHsdFCzBFwJGhIRZ29nLOJRcBsHfffgrT5Z+T\n6njMmfLVCkHPDr9W4qf/+gPOPHcV/YtWUKtWsN0OAAy3UysfZYBSyKaFbJoIVJwjThDE1qyw4fC2\nhznnktfgZvs5OjJOIT8CQHnyAH5+BM8vMD19mACdWF933W10LjpnVpndVBJpHKsWNCwH2xVYySwY\nOsdkKr2AlWlxtNjgp48f4MhInkagc7MJRyFDaNQVlg3pOKcpI0HoQzJtsvbcs7jqDRuxYo9DKqVz\njtJESYEivkZUx3Fmrvp7aRBMFYo8GhOP/+TBh9mxYydRpBgcWEhH/G5LGYUkYgs9DAMSCYfubq18\nFy0aYMWK5ZRL0zy+81muPeMMAFYuW3FKhRtdGRPDthiZ1htxuRGRsgV+pHBMXWTXqrKTMi6qUuRS\nNl68eY/nKyBMotBHGCZ27AlJKXQ1rGmghCSoR61z2ZZJ0jRwTQvfjqj5zQjMsWKYk8EAUq5NZ4de\niJ12ioRpUqtMEwqFUuDHOdjIDgnDgEgqokjwxHZdkZx2E0RRSM2AnOXj57WCTee6KI5PkUgnGC9U\nmZ4u0Bm/P7Qnl8F2bGRHF46Tpatf/71vsBNpzWwwOIkElmniN/T8rAU+jmOCijCFycLBha0Uv2tr\nQzZl2/Qlc1y8ag0ApTBBLpcu3oFWAAAgAElEQVTFsEyEZeMk9LpCKWqhyTMHi4TYJLMJnLK+/3J9\ngrrnY5kGYRQSxtXeSgnkKTg2fVkX2zRIxvrOEBLHdunr6SaZdAh8j3pDrxPbcnHTKaIowjYt4n0f\nZVq4hoEXNDDdBGH8gWU7CHTBXRhJbNuiLR7r6elpgtAnncuSS6eoVb34fur097szylyr1UApqlWt\nsMNAkkrlWLJ8GbnuLp7c/jT7dutK6FJpmnPOOYc3vGEjx9dYHFNWJ/7eRDPPWa/XGRvT+16xkKde\nbyAE9Pb08Myz+wDoXRBQbwS0pZOzD/jLACEEUso4eqZvIJVK8WL17otR1MeOVRimSTFfoDA5QTKZ\nJoiXhlSQcgRtSd3RkMnp+ou+My06g0F6piZQvk/k6y/sL1TZNbID9zmTJb3LSaPIdujnsGDZYpZ2\nLkSGIXuHDrAg0wNA10A3ypo5vz1HBVegpNIVQ4BUggcf+L/kx57lV9/7h3Rlu6jF4am66RMGAqFs\nQLY83KBRxZ8exzQUoWGScLVVcuTwM+zeuQNh11m69Cjf/IcvUynoCsueNptkwqQhItZccAl9y7VV\nksotQJ6CtV2LAiJTtFqkhGHERgCA0AVjWi2gRIJnD+xjz9FJJicmcWMP94JzFtGRMSmVSrhJGzup\nFdlzeyao1AMikuzdf4gDB4c4c/WZ+kJBiIpDoEJIIqkXfBD4rcjAL4Jjpf0SqRS79x7gR/f9hH9/\n4AEAjk5M0dHWTn9fD+25HKHU9xJFAUFVYpgWSkG5WmY8fm/rc3t28fgTT9KezeJks/zHtscAqNfK\nrFh9Jp1dM3tk0jA5MFmj5MUhQgSlUCIQJGwD0xQt4w2hF5DAYLxYpRovklAa2JZASUWkQpTfLBwy\nUUJgKQOE0m1EsUEQRboq2rAEDhbC0d/RztIsyldBRy4N1TAetwkaXoTrmJhC6Ss7en6EYUjkB5im\nSco0MOOwr1ICw0pA6OGXquTiKuipQoGVizrp7e9nohLRKJQRscy97W3Yro2yQ8YKw/TkdGFH1IiQ\n7syGTsKxKeQn2b9Xb8i+52PZBul0kjOWr+DM1atYvWoZANPFAs9s305XJmT5iqX0L9TRjAHLxnEc\navUqSkbaEAJEIsezoyN89UfbY8NHkS/oNMnI+BQrViwjmUwjI9VqvYtOMUpz/sql1Bo1bSgAnbkM\nWTcNpkDKCNt0ScSGqWkahFFEGAQIBXb8d19GeL6HKaCzrR0/jKNTkcJ2EmAK6l4D4fsY8RoJGg3S\nbopk0kZ6dUxXRwXa2tpY1D9zBb9hWICv9z60HF3dXXT19rJz917KhSmM1u4iyefz+L5HKpU+Tsnq\nfJ1qFqGa8X6jFApBFEUUCgV+9rNH2PGMDnlGUUilUqa/v5+LL76E/JRuX/vpT/8DlOLX3vnLs473\nqUM97+cTDQcBOqp5rNr2uMPECd+IfY5mE9aLxvEFV0opJJJqpULoeeCmqMYGZ6WQp1GtkEwqdu16\nBhE7ZeV6Acdw6GlfgGFDFAvRKSMMIfHqdaKwnR07d3DgR7oFsbutkzPWLOOMC1ewuG8xfk1HJhcu\nHCCqz/x+6DlSvhKFalUdZjJtXH3tr7D7ibuwHch19NIRl/Y3DJvx8Qalcg0pwLH0wIwOjfDov2+l\nN2ey5W3vYtfOnwHw4zv/gZGjI+zfuw1LwvZndpJ0tXm89qZfZtmac/GVw4qLr8JIaO/alAovmD3P\nNDpyBBlFmMdN+Oa/BrqKNpXUm+X4dJ1Do1NEEvxAEnr6Iew9sJ/LLlrKwMIuTFOxe7+uZj08fBSD\nNBJB1bPZuWc3q1fq8KxrWvgyrrs1IlD6XFHkn1KIbiYcXwkdBCHbnnyKr975XXbs2kO1rEOxbe2d\nLB5chGPbVGpVwnisHMdBqZAw8DHMBHU/pFjQyleGHpOTeXp7ehlYuIS9e7RnNzY0xHkXXjCr8t2f\n91DCIi4mxTJNlFAIFIZuBSfRrIoVAhVXz5d9RcPXY2JZQvePGzpKIuPVY5kCUxiA0HX1KkTFlbGR\ntDFtB8e28RuNY1a5MGGWsLNXq6EIyKa1hTvuGAgpSDg2fbks1Uq9FaHxPR8LA6Uicq6Jm9bjEQmL\nfLlCOjLJJBKkknq+T9bLnHXeaorTHnsPj9Go1VjUpQ23iJCutk7ywTRRFHBozxAAJU/Su2JmhRCG\nIQcPHqTR0AZdd1cnCxcuIJfNYFuCnTueYfiwPl+lViGhPNp6kgwsXobhatm8IMIPAkrTBULfp6ES\n8ZBJXTHckCivwWRxmnJJK9+eni4uXnsBCSehU0qtljt1CgFVSNkWSSuJlYjbqlIJsskkofIJIpBS\n5/dAh119P8A0DdpTWZp1+ZV6nUatTl93D1KZre6HkBBFiLBtCtMlMnYCI1aYmWQSLwxxjJC2hGAs\nXtf93SnOX3P2jDInEi6e12gFbExhsHjhABNHDqFQLB4YINfbDUCpVkcJizCKsCyTMNTXMS0LlEKg\n2+R2xu/n9YOQrp5eRkfHeOqpp3j66aeZig3hKAypVKqUS1XOOusc0rGnu3foCD/96QOnpHxnT101\nn5/uPmkqVdXiRDjWvWGaVkuRKRXpanplIxEntDpHMiI0BJYwXpJiOiFNaWrehXKlgh9JDCGplfX4\njB4ZoV5vMFmRZNI+Aq0v2h0bYdiY2GCqVjWyigDTwQjAtVyU9JmciqOrlQKTxVEefuynDC5fzqte\npV+9WStWCcOZdcycKF8pFVF0bME1/JAVq89nbO99/OTub3Hmqy6js1eTUhimxWB/D5kM5It1vHiy\nL16yhLFFAzzxwF2g6jz0E215jO7fSedgD8pLMnxkHGVYtC3S4bGySuIHBivPvQzTzNKILR9lO6cU\nCk0KEzNU2MciGc3/IYRBvVLnqW2636vgCcrTNfx8HtXwieKQ1uHhCtvMCTp7LGzbYHRCPxCpXAzL\npNooUa7D4SMj3HOPJhqwQ3jd+suwUylUGCJj5StV1LKifxE0c2APPfI4f/9/vszu/QdRIqJW0YbB\n0sFFpGyLYnmaUEbHor2GwDYtIiSNyCNQAbW4sOzo8EG6e/pIpjJksrlWHnD3gQMcOXKEC2Z5V6oS\nJq5tYR7fjgWYholSRhyajJve47agCEHDD5iq6zE1DIOUY5NyBLZhtYwmqUIiIZFSMDJdxxSCjoxW\nJB25FJYQ2jAUquUNGWL29pcFCxagzJBsTPyQzmQ4sPsI05NlvHwVh2PEIDLSedWAEMNNoZoRDBWQ\nSwkSyW7CICCZinsOG2XsTIbJsTzjU0cZ6GrHjWOulUiiKnUOHDlKtVrD95p55STjw0dnlHm6WKBR\nb9Dfp8NjZ521irZcWpOaGLq/ublObdtmYU8HfX0WmVwOIy5YicoVvDBEChPHzeDFRYCe71MpFUAF\nNHwdtXjVeWcB8OoLzqezq0MXVxpGiwhGofeH2VCpVujrbiMONGEIgWMbCGmCIQgCSb2m5XAsAyeZ\npBpETJRLqEiv+6oXEHgREQa+V6UR9+9XvRo9CzpItWUYrx8ln8+zJCaySdkOoRdQL01Db45k3B8+\n2N9Ne27m8G0ylWK6VGjd3+qVq1gyMMBgfxfJhIlpORwZ0yQMB0fGCKTAtCwGB/pxY0Onv78fy7Jo\nVCtsf2o7e/fqMPX4xCSlSo1ISsbHxymVSi0lJoTBdKlEri1HvV5lckp7YOVygUz61NJWL7pVMXZo\nm731KM2z4Hs+xWIJNw7jS9cEoRCi1dRHM7sThSHSNBDWS2vCOd7zDQOP4tQk1WoFYVjIKKBU0GvD\ntEySHb2E0yX6+hYzVc0DcEZvD8WGT6EWauUd34tlmKQybfh+ROjpok0zoa9z3kXnsmxgMc/t2Ims\nKIb362usXtNH0KxROQnmW43mMY95zGMe85hjzInnG0USGUlUbOFIQpKZdlafexmTh59ix9OP0t6t\n80z5yUkuuewyegcX4joJSvW4+rkyztCOhzi4dxe7dzxLpaQryTrabLKugTIEy85cycUrzuNVl18D\nQHs6w75H/x03tZ0zL84SiSadpIUpZi/pV15dV1c2D41DtlJJbMNgeGSML//TNwBo6x1AmjbThQKe\nr3DT+loJO8HQoRL7hmpEUUiktOnuprI6BGpE+I2AammarQ9+TV8mslh9/hoGs/0ov4aMq3ajQPLS\nGxGIvQ7B7ud0vu9r3/oOTz69HSeZpl6dhkh7Al3d7dTrFfL5CUzHxrC0x+VFEbZlYTo2ngxImAbt\ncUjrYK3C8MEq0/kpglWriGLPrtqotzztmeAYZotSsimrZZjYpg4XYxhIjrU1YRhEIfihpBZ7fkEU\nUTYMkgmHjJvAtvRYB0FAGEZUGg0d3gPaMlru85anyEmJEAaBEoSxrELJE7zwF0L/gk4i4WHGdTfZ\n9h76FnXx7DNDPPvsEJWJEu2WtvjdRAaEJJFyGcuXcGLPorMzi7QMjpTKWLbLoKtDywmzTlAPaU85\n/NJZS+nLpVvUddv3jDJZ3E0jDOjo6iCT0+mUI0fHSc7CulQs5LFMk0WLdHg6nXaRUYSwTEzDwIr/\nBc02lkgkkIaJ7zVIOzq8nLANlDLoyGYRpkkyq4vudu45zJPbn8M0NEHOimVLee1rL9Zjk07rKL5o\nhiKbtRIKqWZPpaQyKRzHoBiHsZNOOi7GFKhIYVsWQVzgYpoGk8UJntg5ijB9FvfHEYNpnzDSZBap\nVEQ9JkBRTgq7PUtbV45oqMHByWGySV1T4qIQ+KgQKiGYcRgskezhyFSemco2J6Ym8MMab7tRh3lX\nLFvB7p3buej8NVSqNR598hme3bUX0J6sYdnsP3CArs5OurtjQpX+frq6uinkp6hUKmTiiNKRp5/l\nwNAQCdfFNAxMy2wxwNXrPkEYMjK6nyefMhk6qNf78jPW0N3ZMetYvyjEzIMoReCHFKd19XYqmSKV\nThJFismJAj1CP4Mom4wJlYyfo9RVUq9w4yVopRa5Rvxvo1Zl7MgwlXIFw7Txw4BSWZOdNGQSiUMq\n18PipWew/4iOXp63pIudo6MUvBJuKkWzErBerWEBCdNEmCbpthxtPfr5pDpyKMOgs6uLhYtXc3BM\nRw+dVIZcmzejzHOmfJWSrZCEMEBYMLD6Ajq6Ezx6/w/Z8fg2AFaf/3oODx0k9APae/pJJ/WE8uqC\naqHAkcNHMG2HnpgSzPMkhWmHtZddxuqLr6Bv5YUIRys+S0kSl3ZRKx4gYSu8uJI1DKNT4lQNs714\nhomIVZ7mWdbxlTD06R/o4s3XvQnQfX+OmyH0dY9si//ZMImiuOAmilqMXSpuZTEti1TGZbArycJc\nTOu3fCWWo8jnxyDykXFLRBRGTeKsFw0lJcIwKE2XePCBnwIwPDyMQuLVKvjVSqty2bYtvNDD9xsI\nGRBKHbbzo4hMOk0qnQIliGSIGYco+wcWMTE2Rn5ynN0q1P2RQGdblr6+2ekD9YZ/rM/WNA0cS1Pc\nHaOYjNuApCKIJI1Q4VgWHXGoNl+pEkmJF4Q0/AARKyLbNvE8j3K12mJOy5f1Jr5t9z5Mw2BRbw9+\nELVCTTKKiGZRZGHg4aSN1mYRqQC3w2L1+QvpW9LJ7mcOMLRT9zvny3ndIx0AfkQipZVyNYC9h4fJ\n+wo3kWF0QlfGtqcyfO9HD5GwwbYS5Mt1Jou6kNBMp1m1aClK+Pihh0Av8iXpFP0LZx7rSqVKZ0cn\nHR26VqFJmWqZJoYwiKImH7DeGKNI4YcRlVKBTFZ/xzIEtmXgprpw2/upxBXdDz95H6GRYNWaxWRy\n7fT09JJKaoNBqmNUsMeHNE+1sGZBby+2CKnWjm1oQRAhMUg4LsmExdCIpuY8OHKUBf1JsukMvlek\nI26danMylKs1wqhAqqOXRMxGVAtddu6cYEFfA9vIkci2sTevw8E9rkOXmyRt20gBlbg1KF8ICWKW\nspNhYuQIb9i4nuvf/EYA7r33/zJ6dIxtT+9g21M72LP/ILWaXlupVIqkYVKanqZeqzEccwcnk7tJ\nJBIIIchmszTiiu6pfJ5kMolSCsdxiKKoJZvvN7DMiImxo4wNjxL62lFxLcXhfXtPccRPFbr+QkbQ\naESMjOi8and3JxgOQSjIZjsgMmK5K0gR0t3ZgSF0LlvFe2WT8vOlNiEdP68s0wAl8bwGqUwOK6GY\nKmrlq2xBZ3s/PU4S3z9GNduXTjHV3saefJkwCjUzItDe1oZjmdQMRWAIMh1dZLv1OhvLFxgZGkF6\nHmQ6qCi9rocnJilN52eUd25yvlGEErRozkTgMXFkiFzKob2zn5UrFvOT+3S+c88OhxVnLGV0/15G\nDu7FcrW28UoF2rpzmDYYhkN3/woAzl77Gs549UYGVpyLk2ynESmimDA9EpDuXkzfoi4s4WPE7Q2E\nAjl7So9az0oapkVCHG/BxO3bKiKVNnjV+dr2PXi4yNRUFVNFuJY4geotUgoc/cKD0NCyNfuYLcvC\nUjA1nqenS1vbK1YvoN6YplIFi4hmnZGUEvkSta9E5xgO7N/PkaEhALKJhK6TkBG2aRD62oOcODpO\nLptj8eBCxqcmCet6wVemp/HrVWoVl7ZsjmJhqtXO09G/ACvhknFtnCigcFQrnQvWXsgZcdvRjBC6\n0rFZBWubAstoFlBpLvBmvl3FJC0ShWkIsjFNoGla1P2ASEnN19yklzQFDb+BY1mYpoFlWi3PvNJo\nsGv4CLl0JuaDbXLDzs6D+/BDz7J05SI6evUmHkY13UoTKLras1x66S8x0KNzQA/+5Emmp6tYgaI9\nlcRy40U6mSdfk2A4VMt1igVd9GaZFqGUdGVdFnSlWTC4gNe+5jwAFi/KknRsilMTjI6MkIifQSJl\nkmqbhUBBKRYs6MM+ru+yWV0q0EQ25nF8w1ppGpSKRTLt2mCxHBcn6WKke4nsdvY8p6tsD48cpX/R\nUiLDIZPNkk6nTiiGPb7H83gq1VOpdk4nEghl0dGmPT/bsmk0PLwQEomIsckKo5N6rEdG9jDY+xou\nunABU0dHaXObLyYJSSVsPAISSZdaEEd0vAiBZGj/fqyEYMnilew/OATA/sNHcboWUFFljFQb9ZpW\nZIERYtqLZpT5Detfz63/73up1PQm/MyOJ9i77yDP7T1AI1CYhtnK7bqui23bx+oU4gjM2NgYUkoS\nMS3oMaPewLKsVgGl7/sEDV0b0tvjsmRpDzJShH6INLTRlM0YLG5fMOtYnwqOXxlSQr0e4nmKZEpf\nq1ZXNMaKRGGEDCUy0LJNjhepB2U6L0hjmyZCmMQ1Wq3IqM4fH2egnWKrkThOsMmJcaqlaVzboqMt\nx9HxYQoVLcPK1YN4ymL/gd08/LO9nHXuq4GYatU0yKWySHVMObpWAmSEYzqEwkRikI65D5IJm4Kq\nI5JJTCGJYkfNVxbGLPSjc/NiBaNZBdf0IGFyLM/PdvwrG7e8gWTbIpbEG/RTT2/j+1+tsuX6t4KT\n5OAuHRI4tOchavVJli7tZMHgaq779f8NQLZ/Bb29GaZLEdWah20KRHxXEpN6I4+t6jg97ciKVi5K\nGlqoWTA5XUNFESp+a0xTYWp+aZMggNEjuoH9wP4x9u0+SH0yj5JRqyXAtm0dyjNNwjBseb5RGLZY\nrywLDEex5jxtUNQaPnYiSRhGSCRR1GzRagZrXspDEDTqVQ7teY5srM372rIcSrqUKrrBv1nMFXge\n02GBlctWYCIYPao9rmkZMjU+hesmCetVpO/T3aYLU5Rpgm2TzWXpDnysmlYCV265io6YiGM2+UB7\nVACOpa1gQ2gvWBgCEa/OUEEoZctqFnEUI5tyySZdar6PJ6O4wllXhqbsBK7poFA4to3nN8lKBOWa\nx/BknkW93cf19hqzsg0d3J3n0NAk7T1aIeSyFh3ZFImEiyHK+F5AcUp7Nu2pFJWpMgKB54ccGdde\nWiUIsSyH3mQKgSIfFy9VZUhvJs26tWexsCdFJpchilv1JsemIFRU81Us1YaIiUGq1Sr14swMaJl0\nmo6OXOveTCeBMB0k2uN1TmBh050KSPDqDYaHD+p76R0gne3CL5WY9qbZu1+HNaUSZDJpImXGRTSq\n9cKU5/ONH1MiokWcMRMSloWUBum4cEeFioCASrXCwSPDbD8wzPJBPc8uPmuANgekV8KQkva4T/3o\n+Die79Pe042JS1+P9l4WL8txcOQJnnhiHzhZyjJCJrRxFiRthsYmMKRirLSb3oxW2G1dDiNDh2aU\n+bd+510MDKb42j99E4DOdpfurjb2H54gk+1EyQgVh9zDMMS27VZkpjlWURQhpSSKohYrXnM8m8f5\nvo/neS0OgXPOX8TCRSZKeqCsFg9+h9tLsvzyklvE/DVUqj6FfIViHFEqlSuaXSzwUUFI2tFGhkgl\nSGUsvGKFRNIFJ4GIPU8ptIOgUK37hWMpipkglN4r6jUdOZrK5wnDgLSbxDQN9gwdJp3RkYpspo8f\n3fsASxblcBOgGvoZ7BvPE0aKjG3gK4nd5JJQgeb1TrpEUpG0JG4sUl8qx6teez5PHdjNis42Gke1\njqk1fB1FmgFzonxN00DJADO+nHQ7aOvu4fDQCHfd+V1Gx/J4MQF7rs3i4NATfP0fJlm/5a1092vr\ncuzIII0jZXr7FrN4xSD9A3pzz/R1kEwJKnUPIUIi4eDHVYxBeYLCyA5qScmiwWvAiBmPUMhTIPuv\nHdqF4y1Fxa1LTZo2hcL3JU9tO8iBIR2eKtV8ivkC5ZGD1Gu1lorM5bIsWNCL4djUitNMxu0AvueB\nECSTSUrTBZJd3fQuXgrAf/zHs5x9zhl6I5MRTXNOIjB5Ca1GSmEaBvkjRygcOkgqnjjtqQSDvT3Y\nrkWlUGThoH4jx+KFixg5PEK9XKEjm8OOGb5qlRJKhoRRRGW6gG07LTak8nQFv97Ac0yq9SIXXfYa\nAC665LVxj/fMIhqGgSmM4169KADd32saQr+hKla+DS8kCDUJhq5wNI47jyDlOmQMg6St51ulHlD2\nQ/3mEyURHEvjJxIuSDhaKNLT0UYq9iIDxKwG2qr2HMqImC7r+ZbP1wnSIalEjbRjYls2bfFLMVL9\n3SSFolxrUPV9Ik+HCPvTNmtWraQn187Y6Bh1L7aoMxkmCmUmRscpl00ytkk99mwapknCdBBBhApV\nq5I0lNXWCxNOhs7ODpy4BxfATrjYtouBbhkRIsKIozNBJEmZETTKkHaoxmG0esOje0AyXYvYd3iK\np55+DgA/CLAMA4FBGJPpW3HeXcbPuKmEWx1dp0DjCbBj/wgduTQiZqlLJ1N4XsSTO/aTzKWoeAEy\nJrPoXdRNvlFm/9PDLOzpZPcebTz2dXfT1tnN8jVnsfvQXvw4nZOyUkgqKDNN/7Iz6FuxhAWLdU78\n3u98n30P7SeJTZewCOLX8W3f8Rxj9Yj3/cnJZR7sTPLgXV+lPjqkZRZJVq9Yyuh4kZSbQCnV6haQ\nUiKlHpsgCFrV4KlUKq5Z0PP3+QxRKqaCNS3BuWu1zAuX5RBKE80IIWIaQOhpzzG8ffusY63Pq1op\nmBcim/n/2XuTJ0uS+87v4x57xFszX+61V1d3oVeg0UATQAMgSJEARWqGMuNiMo140lE8kjcdRhfd\npL9ANiYzjbaZ0XCGw+EQAkiAS2NpbN2N3muvyqrcXubbY3fXwf29rAbQmTUUrW0O9TPrQ1fVi/Dw\n8HD/Ld/f9zv3o2oNs1nBQX/IzRv3FjX5vCwpy9I4CVqRCeOEJizTjiMO7+ywOxqwdukS3Qtm3Mpm\nR0VdUxYFrv1+H+XwRQhQgsMDg+wulSJZXiGQLvuDIe+89wFrbeOc7e/1eePNt/nKV/4J5758jvt3\nzboZZkekmenfd3WNY6H1dVEjdU2nGVLNpjSikFbDXOvCxSf5/f/q9/in//M/xffGNAPj6B0ODkmr\n0YlDfox2fmyP7bE9tsf22D5m+9j0fKVS1Lbf7sY7P+S1b/4Lbr3zDu+WFToI0Z6JZM86K1x45klu\nvfcG//x/+Z947iUTQX3+y19hubvJX3/jP/Dd165x5SXjbf+SChC+orF8Do3PbJyyc9uAtwa718mm\nY1bOPMmsqBZpVVUrtHO63+HmKUIp5ignJRRKS9CSvd0dRqMRO/vG03pwb49unOD1Oty82V+kP9Js\nwN7ePdMDV9fUNs1UlhVREBkAUxhSa8H+ofGU1pYCHlx/jwtPXkG4DkibLtdQFqdzO3/ItInYlVbc\nffdNJnt7Cwk+T5dc3lxhtRNzHc3Fi5cXvxkMjnB1zcbGOrH1PJ8+f8HoYN65SX8wYGNziyK1II98\nhFA56bQmdgVf/upvAtBoNR+pN9kRpsa78LKFRjiW91ualHRuw9WiVjjOvN51LMph0tBGoKIRJ6bm\nCKT1CE/lBIG34JidS5sJJOksZZZNORwOaawZtigJizT3R9nFtRau5zBT0t5HEwQenhT4rqCu6kXJ\nYDiZsdVrsbJ2Fd8L+ODdNwFYSgKeffIib3xwh8HREYlF0z575gLOExd4685tklUj3zfHCDquhysc\nRoMBeVZYwQAo6hL/lMi3u9Q2ADa7pq998AFaO3Q7LeI4ptOK8fx5Ea7GczTbe2MeSIeepdFM+0N+\neuM+/SmMU8F71032p8oKussrOHHT1Pnqmjm3o0AvSgtKadQ8JSc+JLP9kfaj92/RjlxWmiYzcenc\nFjWKUZYyUQ6rnTbdxIqcNGOWGzEHgxmdTgehTTTSW1mjcqB/0OfGjWvEDRPJJ+MmbiPh4jMvsPHc\nU2xeWuWmjebf/sk10pFABJpsPGHXxivjouRgcLK8592f/C3XXvtLatfUWX/wk3f4wi//ErHnUOQl\nTuAtMgMGZe4yZ7RaZAyUoK6rRXp5jiWR0kFKQVUVqKpm62zC2bNmHdTlAYaBLwA8XGl714uaDicj\ncAHSvCKdZvg28vQDF+k4H4qA57mLPC85OBgxHs9wHJ+VFTPXYRghhHkOV2qE/Q504JO4mundm/zo\nb77NJz6X8/J5E/lWyjD2dIsAACAASURBVETcVVmg6wp33iT/c7joX2SCWqtFdqbOxvQHh4iy4v7O\nfZxqRrexZZ9vhpCStdVl4u6INc9ypuMymSqWq3WSqINnEdr9vSFpVjLNp7jdBnHtLfSw8RK+8a3v\nsv9gytBpAGbP0eWUWp28sD8ebufakJv/3bf+PQCvfuNfsRw7rG1u8PRLv8ITn3qZsGmg2650iROf\nG+//iK//yT/jr7/xbwC4f+8mn//Cr9BdX+P2G+/x/o9fB+DZlS2ye+/ReuKIjRc+x269zwc/+A/2\nxgFXP/VrLG19gsODGVU5X9AgHwHtnGYpJTWe5VaudUmtfOpKgS65dGWT3hmzWb/21z9k78Y2QRgh\nHYfaclJLBLUyGzGw2GkqNMLxqSuJ6wU89fwzvPyVLwGwEk0JykNUOoE4Qli0saorsunJSio/bxoh\nJZOjAbfffY/JeIyyQCSnKGmIClUVrLbbhFaxZa/fZzgaouqcShWoOYCtqhmnM1RZ0gxiVnor5IUZ\nm0ARhz7peMTq00/yxFOfOB7CI5SZ5g3yjnNcC9RaIcWxJm9p6+VCW8S8kLYufAxuEwhcx8PzAkq7\n+EulCXwfB43vuBRlsVDFko6LDhRlVTJOM8O6g3EG1Clo595SE6kVLXGcqk6zGVmek+XQ7/cXm2he\nKYpaEzgC8hmbFm28lPjU6QQ0NJpNWq5NN3qS5aUEWGMmNeH6Mr4F51CUFEWBH8Dh0XCRQvfdcOFc\nfJSFYWAIPuz639vZJS9qBkcRURTxxc99ZqH+tdWLWGsFDMcN7uxNeTAzY8vLGdfv9+kPM4QTsjcy\na+DZcx0ubba5P9LUQlCU5UIzVyll+ZwFWZYyHE3sHHZxHuH0naY5qIqVjrkeFoB3ZqNNfzBgNQ6Q\nlaFR3D8oEbR5/uplpJaLlpHOxlnef//HRG5Bnh3Q7NpWNDmDzgrnr6zgtBQ//Lu/5dV/8yoA/XsF\nvogoakVWO1RWIH215VOfsoXc3b5HnpccTsy4rt97wOWjIWEU8aA/IaxDpLRMVlZL2tTij4VmyjKj\nquoFMck83azU/M+MJvTSsqAszX1ms5QgcOyHEpLY7zpxEnrnLp46199/7XWyaUXDdhFEsU8QByRx\nRBCGRFGwUHfa3R1wcDAkiVs0Gz2ksE6tkGhKNDWOUDhzvIYLvqipowYbm5cQ+GRWfES52gC00gop\n6od6HwzG5iTTCNIs43t/+20AjnZvMBseIMuSIHR4am2TjiXD2c9TpCuRBLz39vVFetgJXJSsCEKf\nUMyIY+NIrJ2BWkdkqaacacZr0Jua9zMcV+wdzbj8xBfZHx2xZ59FOhHV9OSN72M5fCUCITxWV80B\n+/IXXuHy+XOIpMvlq19CiWOGU61Nsf25l77G5pmn+Oaf/XMA/u4v/5Sf/PhdPvXic/zO732NxHKs\nPhjcI1xeodzdQw8GxEvrLG8Z8JbSPiuXPk1dCIrMeJTzezxKNBbLFIccbet2ulaoKqUqAu7dvoty\nWlx52khL/aN/vMn/87/+H5SzMY0wWnhgjpBox8CklNaLBeUKh8gPEArCVoMvfumLdC3V3L0P7tK/\n+w4vfiYipKKy4g1KKdLxyd72z5rShn36wY3r7O8dUAvBZGLq6yrNKcoZ/emMpfWLjIb9+VtASoed\n3V329vcWTC1VXZOXBYkXsLG2gZAC3yq7lGnKeDLBLXJ6axu02u3FmB+lppdXCuk5C3o6E8cK6loj\npKDUNeVc0cZx0EKaf/EQchbmlJ8RYRAwns7BR5o4aUBdk6Ypg/GIsp5LucWEfkDgB2RFtUCTu0Ii\n3JOjyP54QgDMLEiqUIrJbGqYlLQky3I8G8mWtcaLEsZFxeCwv1DY8pXLSrPBuXW4sNkjsjzEo0Gf\n/uQmruOSlTU7ByOSVVNnSloRSggyKZhIFrXwRhJTPwKQUGmFZzf3c+fO0Gq2WF1ZptFI6LYTZkOD\nS3j6fJf1uGY0EfiOx90dc1CkGnrrTbTTp9dq8OJFs0l95fNPUsg2177zAZXwyfLimMnKtujVytC1\nNmyfteZ4/CeZ77kkjYRGw4Db7m4fcv3WDfLpAUFYkE8CcutU/uiHbxBHHs8+/yLnz10lt0jbv/j3\n/ztKDdk6c5GnX3yF2r7rcM2lH+UcDfY4fPN9fvDtazz4wL4f0USJkgmaIlc49loXNxs82TiZ4eqn\nt/bxojV8W0Nf6rV454N7LHVXCMY7KFWi5t+2zpHSJYwCVC0p7X2Uqg1mRrPI9MC8dx2qEuJY4gYF\nh0NziBwdzkiSiDApiBo+IWZf8ZRkxukMV9959QdIESxqks12RJxEJElCHEckSUwcm0zleJSitcQR\nFXmWUpYP76sV3eUWla4J7bekkVRokl6PT33xi+SO4MByTzeWm1RZyZ2bdwkCh6Rx3ozbO319aCEo\n8pJr7xjkvcwO6DVCXFnTjXzCyEVaMOfkcGz2lFRw972Uo9Q6LblPVpd4kaCZzFjpmXfjCkGuBjjO\nEEe7+HFC0DZj6rahfS6hO2zy3Tce0M/MO5DDCbP9k9nmPiZuZw1KsHXBUM3Nprv0j+5xafMcpZLk\nZb6IjpSVjhPCpb1yid/4/f/O/qbk+9/8cxK/xaXzLxHbZvHZ8IBp1sdvLlPt92kvr3HlaUNl+P7r\nP2R4cJdmZx108PNjOsUur3fIJyOKyniARVqQTmcU6ZT3332f/YHi/RumH68dNcnyGUWegZSL3rWi\nrhb8vkqrY0IBoVGOQHsOIgj47mvfo7CpuOlwn8nBXdbW1zh3tsd4ZhZHPitIJ492+C5aOaQgz3J+\n+pMfcTAeUWXFAmw2q2vu7ffppxnnOuvMrD7p6voZVlZW6B/ukmYz5p13c6dFxAlOKyYvCkIbHQau\nxwyFKwUbm2cWIImqqh6JylMBpapxbD+gFA6eK3EcQ76fVWrR+iOluxhTzTF4x3VdfN8lsh76zPZE\nooxHnWfpght7HkXn4yEqSgiCiKwsFi1IQRAtpCk/yg6HM0LHobTar4NJSlpUlLUCx0PJgEkxpxMV\nuDJknJZMq3pBXxkoifYDnv7EGlLXi/Hd9ySjbEboePhZzajf54M3DbXg0tYaK2fXKRyBbvhIu6HK\n2MM7xWEQwvbaWkdmbbVHFAa4UqPqnJ29CWd7ZmPdWuvhlCPCouSlq12ePGvSc0f5Ktdu7PHS02c5\n39VM9s034KE5GBxx4+YtjmaKp69eYblrW4OsAyoRhGFAaDms64dT0CeYS40nBaXNtPSHfUqdsdZr\nIN0mb9+6gV8ap6HZarO+HBI4Kfv3r9Mfm3Ug9RBBybiqeO6FZ7l17Ya5Vn+HW/fvU44Eb/7oA+7d\nPcQRZnxGHMFFColyJY79lksnIZuejCz/Z//3f+Czz3+C565aec3L8J0f3yK82qRWBUWhmEzNmMMw\nIM8UK94KQRAudH61Ng5hVRoxkDQ1z++6Et8PEMIhjEBKj6NDM569/ZRO6REVDqNZjheZtX6vuM0P\nf/g6L50y1/u7B3Q6qxweGWd8VS2jlDY83UIQRxHNlnGufS8kChOGwxHTaUaa2nJbOiXPMz75qec5\n6h8QWcfIbyfEzRjX93AcH61rHjwwpbtWmpJEAdc/uEFR5ayv9+zcxKeMGA6PDhFacfaM6b7Yv5uC\n4yOUBOFTljWe/baHwyN8z0Xrmqoa04iNE7W/J5nMQjJlynQry2be2s0QJRskiYOoCmZ1xjg1ZBqH\n40MKD/yozTQUiI55b5ODXdLR9olj/phINmqUrogS47lvbb7A9be/w872XZLWJxB+CDZNa1KPriFS\nKFKj+AJ84T/7x8jpgJWlANcJaHVM+qS9eonx4TbTySH9ex+g0wml5frN04L+vfeJGg3TUzbfpKSA\nR2C4eubcBQZ7A3TUsGPTuE5NVc4QNYDL/r5ZoAflLoEnkUlIc3UZZTOEZVlRKiOthdZo1x4VrkO7\nuUQ76eL4Dvd3HhwrvSgYpZq9nV2unOmQ2Wi5VuA/IkZu3iena7jxztvs3ryOripGacrAesiDLOXu\nwZDu8hJHkzFBENv3pWk0W5w5e57t7buL5n1XOgSxR3dlDUe6uEKZFDwgVUXiQwqcv3Lc1/voPXqm\n0XfeFqHmwuKOQ6mgVIvsIWjDByukxEEj5oeygFajSRwG1Eoxtejgsq6RRUZZlriuiyOdD3EL11VF\n5ZRWQNw6SoBzCr9zVWlSIMvNoTTJS2ZpSa0UceRQAdmcwllDWNXs7x9QVAW+JZ8otEOWlyghELrG\ndcwPts5u0Mpz6jQnmxWcCTwqG63u7wypgMaZJsKTSGnWdYUmPAUZOm9R+dD/VyXtwKM/mZJXFXLV\nEnBIiRY+fhAhRU0QWdKBQnI1i3AQuCIlsBvxTv+QN955wF5/yCgtuHHDp5GYg7zViIjsgWv61Rcv\nng93jf5iW24EuFIxmlrN3sDhyqUNstEDbm1PeeXTL3Ltznv2X0uUr7l964DA32V5xew7o6OUW/d2\niauav/kf/wekdfSascOd20N2djKORinaUSShmdMobBH5LTqNiGcubrB93xwU2w8OePqp8yeOeecg\n5ZuvvktmndqLl9Y5u7ZKkac0GjH37u4v9g/f96irQybjMUu99iLtLAQUeclsasQi5nPoeQ2qqsZ1\nFJXKuXltxszy4Fca8iwjTzXTLKXfMtcaBCP+6vX3+e9PmevR0QBVuwsVoNFkguc5qLqmLEvyJFlE\n4GFYMpulNJI2URQynRoii2YzpNkKcRzDmz6wSPRyPKDdWyKNYloRBL7PocW63Ll1h7NnNpiMp3iB\nR2a1oMu8xgtOXtc/+OF3WO0s0dwwuBXZ6CDLKU4+o6RgKn086yRXsyHLkUN/knNn32VmnxMRId2A\najamVBXjzJazVMU4y1lZcVlu+rTcJolnHAO3WuON9/a5s92ncidIWyL0q4JaPOZ2fmyP7bE9tsf2\n2P6Tso8HcIUh2dAWobBy5irN3gZFOaJGoOtjndo58EbMPWKbkjp35Rl2P/Eck533qMpqwV60eu4M\n6cYao+GAo/4Bw/EIPzKe7hMvfw0HH0mMlB5CzBvU1SNFvkGcUCOwcrZIoQCFYwXb93cPadn0dxL4\nRFFIIEOWN7poTPqjLArQctEoX9g6UyVAKEGZVVQqp8xnFFbmbTLNmI6PuLe9w/jp80hbV03iiNA7\nXQoRWFAxHvUPeP2174FSVErTn0y4f2hSXQfDEWle8/TzZ9g/OKBlOXqzLMXzXDY3N6mqkjS9CUAY\nxQShR1XkiMwD5xhI5jqaKstpdpa5dPnvGfkKQTWnIK0VUkiKSlHWhl97zrWssYgrjC5saPuwozBg\nqW3YaYo0XcgDzrMpQSDJ8pSyKj4ca1niFNeVD/VPimOFlo+w4axAc1zXLJWmFpCVOX4UEsbxQkaz\nLCuE57C7u0eNILFgME9K7u4c0IgjVrsNEvssTtRCj4ZMy5rl5Rb50RGNxERQaa7p7x+ROYpwqUlp\n0jDUsqaYnuxt65+R8NOYTWApjBnUFaIWi5RnUeQEpt0aEIRNywt8kBKMavZSj9gNqWzWKk1zsqKi\nVkak/MH9XQYDgy9YXVnm0y8+R6vRoCiO6VeFEIva60m2sdrl2p07+IFVVqoqitJjcDCjKgpefP4f\ncZCaLNn9O2/QCVzi3jqhHrO3a9DYO/2Ug0nByqTi/vvb9JYMBmXiBrx384DpNANdE8chPSv5WIqa\ny5d7NJMWg+mIy08YgOW9ezuMx7NTRu0wTXP+5vtGXnM4KVnbOM84zSjymsFwQGZ1zMsCdF2xUx6w\ns7OLFnN1LSwlp4/WigsXTApbSoeyMOv4+o0dPN9btKWn6Qjf85lOCvKyIncsv8FKwnBy+lwfHR2w\nvbNPbYPNOw+u045j4jgiDCM6nQ5VZa6ZJA2kcJFS0g29RWeGH7q0mh08z2V1bZXarqlJnqNqzWA4\nRWuXJT8istrs+zt73Ln7AD+I2NjcZDgw93DkgK2zJ0uSPrj7Adlhi+HYrLdZUXNx8zzdyEfoglFe\n4vjmPr/SW2e7X7C7s8/hqAbXpvj9baZ5jvYrhPAZl7b04CxxNBmyPxgRyxJX+iSWgx0dQFFQzSYI\nrRBW3nRtfYlGcPK3+DFyO89bDSCrM2TcpiG6ZMXUoFTV/GO0gvVzppfFBujRXO6xf+stqqrA9cyf\n+4EDYQOCJk60zNHRLnVlN2M3wqkdXK1BaoTdJJQ+bh86yXLPBSEWgteqUqAchOsgRMXe9g1GfbMZ\n+L5Po9HAszyrcwSnEBLpSLRSVHVNUc5rjqYVQwNUJelkvNj06rqmrmsG4xmH0xLfphU9R7PgYjvB\nNJqZ3Rh++tr3GO7tMU1z9o4G9Edj+hPLopTnNJMW45G599RKsnm+i1KK4XDA4eEhXmAWYRBHqLoi\nn02oHNPio6v5xi8oKs2LTz/H+vrmz5EBnGZCGLTn/PnqWlMIhev5hI4gzSvEnERZGNIN3w8Io9iK\nL0DkS5LAI69qMimIbTtRmpf4QUBRFGTjwmo5W/CWdPDDAEc6lu7OjDeMImbFyY7OrNSUVbkoFxRV\nDY4kTGKSVoO43aYYms1AuCVaSpQU7B9O6M/MWtzdrdDFCo3IR1c5Z7dMW0oYRqxEIcVkwu1btzjM\nUjL7G9/xWW6vstMfMC3lQis1CsUCsX6SzYXY5zatFdcOjygdQ1hSWLBPWRZEcYBLhBCSwIJsDkXB\nO1UFZUZbOwhLaoOQIB1CR5DVmrIu2N01aeLRaITrCJrNNmmacfG8aftYWV1BP0La+a2fvsHrH7zP\n5StXAZAiIFUNOp2ztOLb3Nzd5cmnPw/A5rJgOhvxg7feZ8mVkFtQoB+ydqlNkrh8/pkO9x4Y8o07\nk4wzZ7ZwdMlar8FKt81ax6QVv/Xd1zjc3WUoUw5HI7ZWjBTjf/lrv8TB4YMTx+x6NVK6VNqs23dv\nbVO7MYeHY4QjKOoUx1IQulLiCLMea+2iHnIcqTRaC8LAX4gnuC74fsR0OqEsHXAknkXKNzsBS72I\npeUlsrwkGNuy2STl7JMNTjPfg8PhmKPUrN00GxFISeD7hGFIGIYEoRl3HEU0Gh16y6usra0bTW2g\nrNoURYHn+yRRA9fWVZsYhHZRKhzPQ9ViQb7Ubi0xrVJG0ykrhSa16PpZcHrAcWG9Q+h49K2j9Tff\nfpXdJ67w/JNX2NpaZTY6wq/NYf7p555jK3X4k7/8Pkf9B6xeMF5Gc70kmE0JHIEXhGzvmBLDymbC\n/eENwrhBNirI8gOmuan5pmlNKhVed0aduyjMnDlBRCc8mfv74wNcoRf1tMALjcIPCuEIwFlQBP7s\nZr3wkBE0e5t4ccBB/yYrAxNdrdZnEI5L5IPT9FB1m/HQIgXrGl1ralkbQIma8/aCeIRDDASq1gvP\nXNbaiisIljrL6Kxgdc30qM1mM268/R4CA/yZ00iWdUXtGOSuRjOHdTvCQ0iBqhWVKlhdW1kIEGxv\nb5v7VoqyUGhxDMV3HgG8lE5T/vavvgXAG9/7DkKVbO8dcGtnl73DASOLdkZKlpd7FGVBluVMJiM7\nP4LhcMBwMCQvcrzAMnxpg1zWqmKSGpECy7xJpRSd1RV+4zd+kyAMFvSFj3r41hbM5TrHBK9CQOy7\nBL6L57nMclunFZI4iWnEMVK6i/akxBOEvkPo+zhCMrQk/KWqOBoOTb2qKE20Z8FUSZgQxw3SPCWJ\nQlxba1OIBfjpo6zQkmleo7V513Hk47iCpe4SURRSIRbapAKX0XSC9DyEFzLNzDsdpUOu6ZzYl1Dl\nFPbAb01KlleWiJMGs3SGFpLp1NSTylwyEYrhdEonbFFJ2wJE/UhaqKbddp5FEFQIpqrGkzbyn9e6\npYvwQsIgoX6oh9hZW2a7u81TTeMYlpY6MGyA0vt0IwdfK0oFumFZiqRkdrjD9GiP/iilZVuG1tdW\nqR9hTd+9d5+l7jLb983mmhaaC+cv4uqEjXaL7373G7z4OXOdKxfXub+/zMHfvcm1wwM++6wRON/Y\nWCOOfJ4822bn9m0+2DaH73g6ZT0O+NSzT9Ftxuwd7PLBNROt7u/uE7oFT11ZZ/X8Ck3fRDzl0Yzi\n4GTA1Quf3SKdlQuHMo5jVFEznKS0lx0uP9VjjjR1XEUcGz3r0Tgjt9kUR3hUaWFqrlW0yN65vgGq\nFdWUKFJoSlRprxUm5IVmMC5YXvIJbZbw/Ts7rDzpnzrX7VaEcl2cibnXLJMU4zF5PmE6GwKCup4D\nwhS+F+H7IVGUEFlBlfWNDXrL66yvbXH23AW6HZM16XV6xH5C4MfguWg0rYbBDNS6YnCQsX94yLmt\nmig0joIjHabjiqT50cfV2ZWu7Vowa/FPv/4t/t2ff53v/O13OHt+i1a3y1NzHoO8oNndokynzAaH\ntAKTpfzE5adJU0VVTCm0wsZJdHuKKBnSikL8VoO8FEytgzp0pqyd71I+GDA4mFJYHYAySoiSk4Fi\nHxPJBqCPhY6Zp76EwMVB6YdItXkIqav1MQq6LljZeoKNi1cZ9XfY2zY8s+vnnqDRW8L1QAiPRtJE\nW/F5mVUUeQVCmPTxAi0rEfp0yjKlpCHkmKMLtJEvq3RFu9Wh0+qQWiCBVorJaIQvXfxmc8FlOppO\nSPN00Srg2Sg2jgKqqiabzciqjGq5y9imTKSUKF0jpY9SUC1ASArHOTlKGA2G/N1ffps/+9d/AsDS\nyjI7e7u8+c57zKqC0Wxm0LhAI05otprM0hTQZLZl5vDwiCwzACWlarQ9SLXtM3SlZKK0pcQzfxdF\nEV/92m/yuc8bOslHPXQfNvOb44xBHHgkoY/rOgS+TxCYe42zisAPcB2T/p+DejpRgOt4VDWkRcWB\nnc9pmlLkOVWlDLez6xLNCe193/T0akWn2SBumM21KOsFIvqjrK40s7wmt3MgvZCt3hJxI0QrhzxN\nobKOYFWTFwVpljPLxgvAStxMSKXL+ztHOI7L4cCsp9uvvY3wYr74/FWE63Hv/gEEpk+x22lw+/2b\n7A6H+MtdnNBmCypBcQptqlmHx2hnpGlfEVqYbI3WOHaNRkkLL/Bw3YBA6EV6/ezWEv/5Lz8J+QGe\nK1lqmZSg35H88K17+K4migHhceGMQTv3OgmNToelTpud/SFDi0isVP1IrUaf+czLRM02/+JPTd//\n0XQCquLMZz5JyQaNqOaD1/9fMw/rmzz39C/z5Ree519+4y/43lu3zLztjrmytcbXXvkkf/PXr7Iz\ntgQogxllOkbWKelkxsHhCNcGCt1Ok3Obm3z6uQt0mw0qC2q6t3NEmp68Pj71mSWKTFFZUGJveYuf\n/mjIzRs1W2c6bJ13ENYb90MP15c0kgitarJ63tMukUoRxoLtWw4/eNVE20+trjEaFnhhxPmLK1RV\nsbiPEoZbeJaVpLOKqf0OatGEU0BAAEpKut0u5y5dAqCqc6698wZKafygAcJhPDaOeppOKIuSbJZy\nONmjss+zd7hH0rxFECU0my16VhFrY2WD3vIay711emtrtFotWrYXPHIiwpmHF/p4YUjcNN/1dHLE\nO+9c56XPPvWRY25GDYSQ9HrmWk88dYWb9+5z/c4tHgz6bJ49R+Cb6Pv7r77Kf/3f/LcEnqQRQP/O\nLQCGywWBu4rQCfcf7HFkI+9Az+hEa8ReQjNM0E7Cg0MDlEsBx40IopC4VeNalLyfxBT1fwJpZ9Pe\ncHyI1aqm1hpHzIkUJPVDPbjzQ1LV9QKZKQTEcYvVM1e5vr9N/76pQz7YvseFbpsg8hFCE4TeQvcV\np8T1PLSuyPNiUXPW+lhh6STTan4Y2ChBGb7Tuq7Ji4pZmbN72F/8ez+JiRyPqBEvNrd2p01QRpRl\naZ9lTgghkVIY4gQlOTw65OjIIAVd10Xg02p2ALnIkGspqU/ZqP783/4pf/0X31ikQr/6X/wW/+e/\n+pdMyoKXX36Zb3/720gb9T311FP8+le/iu/7fP3rX+cnP/nJ4jqOI6kqg0yd9xwKYRwD029reGgb\nDeOd/vpXv8bv/M7vEkWR6e39jxQGk1JaaZ15dkIQ+T6e6xPFEZ7r0rPjnmYlaakpa430vEVva1oZ\nx+HO7h6H48kixV8VBbVSaCth5vnBon3BlYJpntIIQ7Y2Nkia5vBN9w8WOISPsqquKJQitfcZzzJm\naYYfuahaG/7pORF+WeM4Hnk5I09zmnZjiWLTpnE4njAejbly7hwAddzhhz96E51lPPvEFv3hjHjJ\nbB4Nz+VoMiMvS+q6RCgzL0UtFq1SH21zTMVx+5iQDq7nIQXkeUGrYaKXTqtBXRUGVe665IVJIXtu\nRq8bURY9oqSJYwWNi4MhaMMaVmlYiiWXN836uLC5wvLKKm6c8OTlc+za3s47RzkHs9NT5efWe9y4\nv4dnSwlRlZFOj3jjp2+xsnGRF65eJLSEFWHY4vaD+3z3zTcZzgReaEliDid88cVP4TsV7YaLZ+lJ\nVpseV69cotdpo8uKNC0M5zfguS5LnVXW2i0kisxmOaLEp9M7WSGo25SoRC7WUatRE/kKoSrOnGlx\n6ZLGsWIZGo9ZWuFIcB2HQM/LIpLQ98FT3L+TU1s07dkLCTv3fSo14dxliSsdsAGFRpBXLmlZ4RCy\nf89EY3v397hw/nR5z2lWoFNFb9lk9WIvYjqa0un2+OQLnyVKuhTVvDSRUpc5+WxKNpswGJn3Op5N\nmOQZWVmwt7/D4Y7JWNy4cR0viPCjhHa7w/LyEhvrpo7eardsD79mmqcLlPjR0YAf/+S1Ew9fRwQo\nDbWtwYynU5qdJoHv88wnn8fxI27brMmtD94jLXJW13s8/cRZisLILD655uIqxdu3Bty//oD9oZm3\n2QHs7WbEfkq73SdsdNg9sLXlvGC0EzJJBdrReBYgFJSwsbF54jx/bIfvXL8W5tRk5iD7RaTdBmt1\nHKXOr4FwaHTPEkQtHFv7qNMxk8M+oc2vS0cbdhdM43UQGCkxeIgmUj0UzZ407p85QEyfrml5KauK\naT5jODMeoOd5gyTVowAAGdVJREFUuJ5DqWsG6fhYt7fWSGFUjcqqorSpTIVxPOal8Ko4VjxyHIel\npZj19RVcxzU6qGBqzb9gvh62r//pnxF6Ab9hdYZffuULDNIJVz9xlRdffBGlFKurZq5+9Vd/lWee\neYZOp0Oj0eDaNbMIh8PhglHn4XmaOxBzveKVlRV++7d/G4A/+IM/YGNjg/ohh+k/xqQw72s+5ZHv\n4nsu0qobBX6AZ+nu4jCiVHA0zciVRln2m9sHA+7u7JKV5aKdCixbllL4nm8+7oeqjIVSFGXBlXNn\naCYtUhvZ5GVxKhBIO5JKKyr778azGfuHEjfyqEpBXpT4Vv7EjxpMijHTNEdKjyctA9jgaMDu3j5J\nN2YmBDds20VTOpy7fB4VBtzpj/CCiJEVKu8PJ+z1j6hdDUqR2Fqsrmq8U0g2Fu/Tlnlc1yXwPdCa\nLC8BwdqKSRE2GjHptEZpRVlVi1SjqwOcIDZsbsKlWDj4knazgSslXuSwudYgt39ZVgrqiumgT7i0\nxfqqqZ2GXp+gfzpl6tHhAbs720SWclAHEQ6azbU1tjZ7hJ7PwGYNxrMJd+6/x+5gRK+3jmv1vRPf\nZzw84OYtH88LaARmDi6dWeczzz2J7/oIIamUg9a2LVFr0A67/QGdZkxoSzBJs8Wh1Yf9KFtux5Sl\nRtho05El6SwlSiI8maHKGj8290lnGc2gQRw2qMqa1Dq8fhCZ3tm8QKqY5WWznuJ4SJGVNNs1jkwp\na4Wy/KuOFEghcFVJJTXtZeOwbF1qELVOnWqKsmYyHnPUMc/XSGLyrMb3YpaWVvGC5kI6T1MhhcYV\nCqkVqcU4JHFCoWumWUZW5BzZuUqzgvEsYzRNGc9y0mKHgyNzYFdVReD7+I7L8GDM+2+bvvbQc7h9\n62Qd4ixPcdwIbdf/0WBElldo7VCVis2NHrffNZShXpKQlQWfeu4pnn3iIq/9wMz1cmeJpfYa98c1\nv/zFX6LTNk7BYf+IwWSKFBWz8S5pBrH9RoaTCTht+rnHyso6jmPpdscp5zrJiWN+3Gr02B7bY3ts\nj+2xfcz2MbUaCUOEoOdUc8IAWyzKWT8MAFE1c9l0E0AdR161qolby3R7Z0mnJt2r6wKhAgb9GX7s\n4bk+c56B0LcSddpFSImypOJlWVKp0xF00jEtQgtBBqGphUIJTdyIee7ZJ9ndNRRiRVmA1pRFyWw2\no7BQfC0EVV1DPa9h68W1pRR4rofnmP98K2cXxTEXL22ysdHFcY8RuI8SUTZbLX7ta1/jS7/+q4v7\n/PKXvkz9hVdwXJc//MM/pNczSM6tra1FJPS1r31twZ7zzW9+k/39fTzPY3l5mR/9yAhVTKdTXnnl\nFaqqwnVdvvjFL/Jbv/VbAGxsbDwyleQvMkcYVjDXIpdD38PxDKG7siWLOQBKOhrPdQk8h9FwuiDT\n2N7ZYZZlqFrhuQ6lrVEqbWrQjjTv05FykXFJs4wnL55ja22VNC8WrFhzHdWTTEtBWdeU8140Lam0\nIK8U41HKcDQmsJGNHyWMpxmzPOPq05e4dNmgfe/dVdR6nXOXz+D5iS11QBgJBocDbt+8i9w8Q7u7\nzOzAakff26WoBdLFPo8F0bgaXZ0yZua6uvPyB8fSb8LQODYaJpJWMkB4FVVaIFyJawUctBPhugl5\nluI7ElyzhoKgpNtuIh1BOwnoLTUsshwKKQlbTZxsQpmPkbZVI0kabInTyf5vbt+nVBXPXzWpx9vb\newyGQ1Z6y5xd6ZBOp5y/YGqUo7QgHB7x5MWLNFur1FaMpBX7rLc93n33AwaTinbTlB667RZ7/TGN\nRpc0z2nEbdaWzTfiOwaENt3rI1yPKHTtvKmFnOZHWRL6lE652He0kqhKcvbsBpEnacZG+g6gGYU4\neFT5GMdRNOy6qXRG4EPkx6x0egTPmbXWiCS+K1lb6RE5OWml8CzYSWtFXlS04hAlNXNeZL0eEHin\nZxkaSZO6VNR2j2w0Yj796Zfpdnu4zs/Iq0rH8qxrfNchFKY00mq0EZ5jaHXRHAwNOtjxfPwwpkYy\ny6ZoikVGdDyeMBoNGezvmhKIZcsaHoy5v333xDHXqoRKGuEbMwns7B6higqpFaFWFBa9fenKFVzX\n4+mnztFePcM4N1TAP/jha0h5QFa5/N7v/haXz18A4Nq1a3zrO6/S6ixTzrq4TogXmTmdFhOuXn2e\n4eCQf/uv/y9mMzM/sucDJ8/1x9bnK4X40GYmML1qdV2j0ccC3kjQtS3/iUW9REoj0F1kU0ajI+qJ\nSWMc7t5j5eKzzEYFoYqJYw93LsjuCyQm5R2EHtFcSCVjkeI9zR4GFS3cACFJGjGf/8Knmdi2ndls\nRp6mFNOU0XjEzKoaCRzqymx6gmPhANdz8Dxz4HpegB+ERBaOH0cxnW6LOA7xPO8XipB/lP3+H/wT\nnv3kCyRNk2rTWhPH8WLsn/zkJ3/h9drtNr/7u78LwEsvvcTR0RG+7+P7Pn/8x3+8mIs/+qM/su9D\ncu7cOVpWqHzOnPT3PXznrFWenB8KAsfxcFwPx9J1zquZqlYIFFlRMc4KhvYd9IdDs9krgwqf8xcr\nVRsnx/VQShGG0YLvt9tK2FheMnVKtHGUMIevPOVZRtMZtdZWMADCwCduNphMZxwOh0xnKYGlNdWz\njMPBiM3NTS5cPkdq9XzbS02anRjPNxva/CAV5ASBzzgruLm9w2effwF9aDYJ6cByr02zExH6HoV1\nGBzhGBWuE8yUUsQC8KWUScFLWxpyHZc4tvrAhNSyphYaVwYLbvushMh3cLwQ6QlYAEs0SWyUemoN\nSSNGz8zY8rwiK2s6vU12+wPKzHwfcdKh/Qh81G9ev8lyr8elrkn31TJiMH2X/cNDWlFEMwpJfDPA\nySjj5Ree45133mNa1ii7UTaaAaPJjPFozCCrCBNzwPYHFeM0p5vn9JaW2VzbILH9xEeDCVWdc35j\nE0co8qkpC5Qqx4tORg5PpmPqWuNbQFyWZlR5wdrKKkLVBH6Gq8whq2rzZkLPR+t8wbrk+hGhJ/Hd\nEFUp2k2znlQp8DyHKq+os5pQegtRkLyscD2PvMxxHcFSYsBOZaOiqvdPnes4TlBKH3cRJDHrzz6P\nkC7CNW1Qc2ESLQxoTArj1GlbGkq1QlbG6fX9gI7tEZeuj+MHCMdjudvFC8QCyDedThkOB+z7Dqvd\nFVaXTdr3p6//mJ/86PDEMb9/Z4cnLlxcBDcIQVFWOHXBV770Mqu9Jd6/9jYArXaT0WDIG6+/QaP3\ngLUNUwf/8le+TJWlNKMGay2fujBnjNRTyukudWjKWa7r49tDNk8LzvxSi6fPbfLma6+ys2/GGXdi\nRsO9E8f88aGdYVHHhWMy/LlAtJ5vekofI5+15mFJHCklUdIg6C4xmJgHG2zf5N7br5EsrSH0GVwn\nWkQ1vmOBXkrjuoI4tl6J8xBV4Sn28EHlSENdIYRGSg/XjwkjW3uum0b9J68oi2JxYEvpmFKzBbnM\nHYP5QSWlQWJjQS1gABeeF+A47oeEpJXSnHa2ffpzL39I2ea4Vevnn+dnD8rEirI/++yzi8N0Npst\nasRra2u88MILi4/l4ev9vQ9da7XWuEIuiDR8z0c6LgiJ63kopXG9Y0BJWikUAt/zEPbArrUiK3I8\n6eAIuUCs4jnEUYzvutTKcERHFrhzbmPDtCc4LkWaLsBlCEN9d5LlVY3rBTj27Gk0I7xAMhymlHWG\ncFlE5Xmes9Lr8fzzz1GhmdpDCWEO3Dqr0aqi0zZOU1kJqqKg2+qyt7fLX33/e0SBea+bG23cwENL\nSZFnZHaj9jyP0xaIwRkc02iadWjaOZSu8V25eG4tQ6bpgOk0p9VtMrN94JWaEXgG5CNrQZ2b++u6\nIAxcXM8lrSrCZofaMX9XaZjmFbJwmBQOoa3d+kFMVZ4ejQ3TknTvgCw1WRgvalMBe4MxVXmfl557\nmuGR2fhWmw22ej1GvX1u90cc2Ra12eGYje4SZ5aX4d4uoUW8Cy1Z6jRZWWrTbTVxqRfSjiPg3v4h\ngeey1EwImwa9PU7Hp2bP+sMZWSoW6HHP8wgbGb2VFlWVMZ6McDyLDVEOGhctA1ARRWEPZe1TVRkT\nMu7em3D2kj1gC0PdWNUlCJeiLCmUmceszHA8h1mWEcQJoW9+M1Uwmpyuinbu3AWkhFlq3vdBf490\nMiVKEpP9cByziWK/f1XhotG6XoiRJKJJmeU40qHb6hD5FuDoOSBBoRAaZKV5/cfmnd65c5coDAlD\ngW6ZbAbA4PCQJD5ZxOJ/+3d/xS99esSW7ZOvZUiz1SEf9VldXebKlct0vmccgFargapL+nsPeP3N\n73OwY7KXSeDRjiMSP+LaT79HZL/FPMtpu5rxwS5+GOIHAsfiHxw9441X/wI38Lmw0eZszwQj1+7e\nwFUnk7B8LIfvoudzDhzSGgdtZP2sxueC2UjaA8uyDs37WoXj4DgS6cVc+eSXeNceANnRPjs33mI1\nHRA1GrC0NM9Xzy+NQBJ4YqFrGbou/ikcuPDzPLhg24CUQikQOlik4pTWiABUUFHk+fFhJAz4YZE6\nXkjmPXwfg6qeH7SOTXUJYbzCOdDoUdLOnut9qEXrZ+2jDsmH//zhA9rzPK5eNcQGZyxp+cO/+f97\n6D50MTzHWbQARXFsDl+LnPXlse9Wa01e11RK0Qw8gp75qMaTMZPpzHjbgU+4eDcKzzOOTF1rWlHM\nuQ3zkTbCAC+MwaZf5+1FUhxHzicMGinkQjawyEsO+wPq2kigua7RCgYIw5CtrS2KvMDxXRrz6LKq\nDPuZkAhHkC4Q+ZKk0eDiuTNcuXyBqiwXLWJFrcir0gC9ZI2w5AFVdXraf85wdQw5Mw6gcSwNkcNc\nx7VSFWmWcTQc48RLVJYbPU1HJJEDtSKQgjo3m0xdpEShRxD47B/mVBoajWR+Yya5Zuf2HqPRbAGc\nChs9guh04oe8rBCBx/Vd43SH3oTQC9hNM4ocvv/G21y5aJDin3lukzd/+jYf3LtLP69x7fOsdBNe\n+dxVxsMxg6MRzz5luQKaDfKyIoo9JpMhRTpdbJyrrRa3djR7gyOyyRErlhWr224zSkc/O8wP2WAc\nU9f1ghjDqV3OPtGjyHL2doeEXYdW+xiAWVQValKiStC1JYfXBa5XI0TCJKupbZ/vNDddIWmWM80h\nr3LmOiJSuKiiNgjqTNGNzfPPZinj/PTDdzSakOdTcvte3/rpm6hSmXl0JcKTuDZD42gJqgJV4Umx\nIN+IgohGFNOKYxzp0uyYeds6dwEviClqU4KbTkb8+LtGvvHBgx06nTZr68vIShH7xplqNxt86ZVX\nThzzG9fv8vb1bVpt24dd5Dz/wgukkyF39va5u7tHZtmn/CDA9+ETT51lsyfZbZu1uNJqMdjbR3ou\nwVKDodUqX19q0XVcbuwcsD/Yp9tbpWG/+UZHEOg+02mJUgI9Md9iqCc0g5O/xY8n8rX/zesE0oJa\nhRBU89YIezDPaRGFEKAeOhTmhPeqJoo6XH7GsNkc3PmA6eFtJuMx4WhIvFagtFkYqhamh1hrpK1L\nAOAKCB+BKUrphRSaHZTVkDUHuyPkh7VkhaAUFQ7eosVk/mMjfF0h7EtzXHkcNSpDwiEfjkakqZaY\n1qY5MtU5FaUtpHgkucRf+NuHNu75weq6Luc2TPpnw5KA/IMduA+Z60g81104W47jIJAooKoVtQJX\nz9ePpCprtDJZgtiKFJxZXeFgYA4/3/cX5CBFkeN7pre367msLS8T2A3ZcR3COCabZSbzYJ/NeYQU\nupSSLC8X/66qFGWpkdLFdQJKVRPZsdV1zfb2NmmaEsexiVIxEWdRlSihQAocG1G4nouHpr+3g+8H\nxHG0kF7Tboz0XPBcs7Efp5Yeyi599JgFxw6WUgrX9fBcB41gpRNjA2xmkxGD/j793R2qShLbtrLp\ndGoOVVUzS4dIe/ir0jT5JXHI3d0R42nJhhVpqJRkWsAk1xyOa9KxiTZm6Yy1lWU+d+KoIQojtNLE\ni2h1vq9oZuWUWw9GZLa+fHenj4Nmlk2oHMllGw2tdmKO9u6wczDm2p27rK8ap219qYWjXba376Oq\nMRur6wysEtL29bcYT3LazU0arRbpnIVuNCWyercfZfd2KrSoiCyqOgoqkhhG44yksc77795hed0S\ntDQCZvkU13Up8wpbzaHZsgxYVcz5s218x0SDcVKyttHi9q0DnMBHePWizcZ1HRzXxXN9pKupavMb\n158Rq9NJNu7cuk0Ue2jLXDYeDZmNxwgHahS10DjKrlPt4KDwJIS+izfPaGpwEZxd26CsaqZ2Xb/1\nZhs/SvDDmDhpEAQBWysm/b+21CVJEppLTZKkTSDN/Do0OUVgjLOrbcpCkdnzxAsC8rIibC9z53AC\nZY3jmvW7v7/PWjPmwb33uH79fTaWzP2rfIYnNWHTR8QSp7QcBlIh6ilXL/W4+YPr3N+/Sdcx13rp\n8ieodIET+ORFTl2Yua6lgKB54pgfo50f22N7bI/tsT22j9k+lshXShPlqZ9h31FzsNVD6dRjxKyw\nEaCcX+T4OkrRaJg+wbR1SJn1CZI2jhtSVYLUcuCWmdG9dD1lhBWs94UAIU/ndq5rbG3smOLS5uwM\nCIh6UV/W2oBYBA6Oc0xfaB7FCrS7Ls48uhXCZgNMUK/Vcap4XgcX0jXp6Tk4pj49Vf4PYQ8L1Esh\n6Fr0axxGH/o3/5AWhSFJFBjAHaa+jWBBamKiUjOPnmOYkTzX0E7OgVGrS13WlntMZzN8x6UuLTI0\niXni/HkC16DeHSls77fRCtW2b7sojqNYx5GnpvmXl5aIisqk0ABHayLPkMxLKcnKnLy2Mm9VRZ7n\njIZDsukM39acpSWaaTQ8fN9BWrSmrkEJiZyXaLRG2DFn6RAlwY8iwk4LLzZeeH0KHSYcl1Lmur9S\nmmdFOnQSn2evrCJqk2472jtiZ2eXnQfb7B8csbRiSAOEUIRRQm95iSwbk9n0XFWbUlEU+uSVYpRW\nbFlWrjItQQma7SWk2yLcMiIek/2bvPXmnVPHHYYBqq4WUX5VKTzXZECG4yFREHHPpqSPpgVR6NMI\nXfJZxuDA9k67Pu/v3wUnYKu3xnrXpELztGSUldy8t8v5tRazSc6r3zd1yE4z4cLZC3h+iPQCtK31\nlXWNW538DSQNyf/X3r00t3FcYRh+Z3pumBkCpCiQimVbtvepVFZZ+g94n5/sjTeuSpVdrqQS0bRN\nkSJx4QADzq2z6AYoOVWUFvGsvmeJBQHiMh/Qp/ucattQ+82XaZzR9AMmTsgnIVdvt3TX7hf2ST8B\n09O0sF0/0PtS3Xa3xfYh8+kZxkT8519u1/DLVyF5MWAiy+1NRxRHTI7caxqEAQbDbmeJo4C7xv3/\nySQhSp9/8LnObcPMZAx+5OKJSTHhGhOGmCjBBpbI96tOiF2zoNiQpimRH3lYlCVZklEWOXGcEvnO\ncfnRlCybkKSZKyuF/3tNc6uK0aHtYYCl/UB9/e/ffE0YZKz8cJq3i4q7RcXmYcfi/pbXF5e8PHcn\nDOLJlDeLFd/9447FcsGLT14BcHW3YFYU1D1c/fvi0O736tdrzqbHbLoNn/0poGriQ5tRmxnuVjV1\n33J5t6b23cRuN80Hf/mONFiho++Hw0add7pMYn+3C9r6TVaHC9+w7wDTu7aF7lzSoUNVXpR05QnZ\npKQop6625He/hoOlKDN3XGXoHpeID/fzdJjtBzy8FzR+ednawe/AfuzI1Hfu2EkURYfwNSaEYPBf\nKjgM4w5xteD9k7Gf9wvueNChq9Z7y4iPt//R3p06s18i3dc2/wiuaXz0zpCChrB1dfB+6ElcPwkA\n7BCT+CC11mKDxyXUo7JwS067msLXfF99+pJnp89haN10odAQ+yYKZXlEVdcsVyu3XO0HE6T+8Tzl\n2dk5Z0lK3bkLQ13dY6wljRPoB47IaXz4ru83pFlGlqZ8+mJ+WF6vdzvCMCBKA7qu9cPbwZgYhoCs\nsKRpSlEUTBv/WVhXNG2DbVqG3cOh4UHXt1j7ER2u/L4FcO+vrg0JsZzPEiK74/rWTb3atQPbhx13\nq4YkC4lSd+GPQ8vN1SWxAdPUVCt/7M/EWKDrYdd2bFvoArfMeV/XNP3A1PR01rK8d7XwbWNYfGAS\nE8D8dI4JoSzca7qpa6r1hiB0vbyzbMLW19e7rqHd9SSTGXkxI/bHX36+fMvDbsN8Pufzl5+x8xfq\nq5slN+sNVR3wRXDCZl1TFu4LzV///Bdms1Oulyua1tL5ZefZUUJvn37csyLg/HTO0O8HT0AYQRlP\nyKKev82/ArOv429ougY7hEzLntAPKNhWLXQx1XbLm5sVr1+7OujlVcsXX87omohfLhZsNg3pxL13\njk8KQmMJfLesfc253jZ+P8LT6mrJZrlg8F8uyjQnf/ac0ATEcUIYBJjIva55UZLnBUWRU5ZHFMdu\nQ1qaT/znOcDECYHvghYE4eNemtBgg8dSmtvnYwl6Vz4ZfPOafrAQPP1ZPJkVBCTMZu5+XhwXPHxy\nSj907B5e8G3U8f1PlwCEeUGUnfHLP19z8+aCauv+9ufn58TLlskkZ5IdU92769302Ss4mrL+7Seu\nL36gj6cYv1T+4/WG+zrk55tb7tYVD5UrDQ0WFsunj0cF9mNaPYmIiMj/jWq+IiIiI1P4ioiIjEzh\nKyIiMjKFr4iIyMgUviIiIiNT+IqIiIxM4SsiIjIyha+IiMjIFL4iIiIjU/iKiIiMTOErIiIyMoWv\niIjIyBS+IiIiI1P4ioiIjEzhKyIiMjKFr4iIyMgUviIiIiNT+IqIiIxM4SsiIjIyha+IiMjIFL4i\nIiIjU/iKiIiMTOErIiIysv8CisU5gpbnpKkAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7feff5f63400>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize each class\n",
    "visualize_samples(data_dir=config[\"data_dir\"], classes=classes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 153
    },
    "colab_type": "code",
    "id": "9oPqQShhvKMe",
    "outputId": "a2c96dfa-f175-41fb-b882-47889cebb9af"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 🖼️ Image dimensions: (32, 32, 3)\n",
      "==> 🍣 Raw data:\n",
      "  category                                              image\n",
      "0    plane  [[[208.0, 215.0, 234.0], [201.0, 213.0, 232.0]...\n",
      "1    plane  [[[170.0, 178.0, 172.0], [186.0, 191.0, 187.0]...\n",
      "2    plane  [[[153.0, 189.0, 213.0], [147.0, 181.0, 205.0]...\n",
      "3    plane  [[[197.0, 202.0, 205.0], [137.0, 148.0, 153.0]...\n",
      "4    plane  [[[197.0, 229.0, 247.0], [193.0, 224.0, 241.0]...\n"
     ]
    }
   ],
   "source": [
    "# Load data into DataFrame\n",
    "df = load_data(data_dir=config[\"data_dir\"], classes=classes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "JNSAGHsI6JH2"
   },
   "source": [
    "# Split data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1AIMrapz6Pzy"
   },
   "source": [
    "Split the data into train, validation and test sets where each split has similar class distributions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "WKeBROMj6WQL"
   },
   "outputs": [],
   "source": [
    "import collections"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "p2SLUqQp6S4-"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "0ySue6PBvKJ1"
   },
   "outputs": [],
   "source": [
    "def split_data(df, shuffle, train_size, val_size, test_size):\n",
    "    \"\"\"Split the data into train/val/test splits.\n",
    "    \"\"\"\n",
    "    # Split by category\n",
    "    by_category = collections.defaultdict(list)\n",
    "    for _, row in df.iterrows():\n",
    "        by_category[row.category].append(row.to_dict())\n",
    "    print (\"\\n==> 🛍️ Categories:\")\n",
    "    for category in by_category:\n",
    "        print (\"{0}: {1}\".format(category, len(by_category[category])))\n",
    "\n",
    "    # Create split data\n",
    "    final_list = []\n",
    "    for _, item_list in sorted(by_category.items()):\n",
    "        if shuffle:\n",
    "            np.random.shuffle(item_list)\n",
    "        n = len(item_list)\n",
    "        n_train = int(train_size*n)\n",
    "        n_val = int(val_size*n)\n",
    "        n_test = int(test_size*n)\n",
    "\n",
    "      # Give data point a split attribute\n",
    "        for item in item_list[:n_train]:\n",
    "            item['split'] = 'train'\n",
    "        for item in item_list[n_train:n_train+n_val]:\n",
    "            item['split'] = 'val'\n",
    "        for item in item_list[n_train+n_val:]:\n",
    "            item['split'] = 'test'\n",
    "\n",
    "        # Add to final list\n",
    "        final_list.extend(item_list)\n",
    "\n",
    "    # df with split datasets\n",
    "    split_df = pd.DataFrame(final_list)\n",
    "    print (\"\\n==> 🖖 Splits:\")\n",
    "    print (split_df[\"split\"].value_counts())\n",
    "\n",
    "    return split_df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "GcunpUPK6UlF"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 323
    },
    "colab_type": "code",
    "id": "yzznm188vKHN",
    "outputId": "e2fa1789-65ce-4c78-f894-21a517821b2d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "==> 🛍️ Categories:\n",
      "plane: 6000\n",
      "car: 6000\n",
      "bird: 6000\n",
      "cat: 6000\n",
      "deer: 6000\n",
      "dog: 6000\n",
      "frog: 6000\n",
      "horse: 6000\n",
      "ship: 6000\n",
      "truck: 6000\n",
      "\n",
      "==> 🖖 Splits:\n",
      "train    42000\n",
      "val       9000\n",
      "test      9000\n",
      "Name: split, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "# Split data\n",
    "split_df = split_data(\n",
    "    df=df, shuffle=config[\"shuffle\"],\n",
    "    train_size=config[\"train_size\"],\n",
    "    val_size=config[\"val_size\"],\n",
    "    test_size=config[\"test_size\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "DlwbzIsL64yz"
   },
   "source": [
    "# Vocabulary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "daGiSGEn7JZL"
   },
   "source": [
    "Create vocabularies for the image classes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "cOH5OuG07Ohv"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "hJzh14kmvKE6"
   },
   "outputs": [],
   "source": [
    "class Vocabulary(object):\n",
    "    def __init__(self, token_to_idx=None, add_unk=True, unk_token=\"<UNK>\"):\n",
    "\n",
    "        # Token to index\n",
    "        if token_to_idx is None:\n",
    "            token_to_idx = {}\n",
    "        self.token_to_idx = token_to_idx\n",
    "\n",
    "        # Index to token\n",
    "        self.idx_to_token = {idx: token \\\n",
    "                             for token, idx in self.token_to_idx.items()}\n",
    "        \n",
    "        # Add unknown token\n",
    "        self.add_unk = add_unk\n",
    "        self.unk_token = unk_token\n",
    "        if self.add_unk:\n",
    "            self.unk_index = self.add_token(self.unk_token)\n",
    "\n",
    "    def to_serializable(self):\n",
    "        return {'token_to_idx': self.token_to_idx,\n",
    "                'add_unk': self.add_unk, 'unk_token': self.unk_token}\n",
    "\n",
    "    @classmethod\n",
    "    def from_serializable(cls, contents):\n",
    "        return cls(**contents)\n",
    "\n",
    "    def add_token(self, token):\n",
    "        if token in self.token_to_idx:\n",
    "            index = self.token_to_idx[token]\n",
    "        else:\n",
    "            index = len(self.token_to_idx)\n",
    "            self.token_to_idx[token] = index\n",
    "            self.idx_to_token[index] = token\n",
    "        return index\n",
    "\n",
    "    def add_tokens(self, tokens):\n",
    "        return [self.add_token[token] for token in tokens]\n",
    "\n",
    "    def lookup_token(self, token):\n",
    "        if self.add_unk:\n",
    "            index = self.token_to_idx.get(token, self.unk_index)\n",
    "        else:\n",
    "            index =  self.token_to_idx[token]\n",
    "        return index\n",
    "\n",
    "    def lookup_index(self, index):\n",
    "        if index not in self.idx_to_token:\n",
    "            raise KeyError(\"the index (%d) is not in the Vocabulary\" % index)\n",
    "        return self.idx_to_token[index]\n",
    "\n",
    "    def __str__(self):\n",
    "        return \"<Vocabulary(size=%d)>\" % len(self)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.token_to_idx)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "W60Z1cda7Pv2"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 85
    },
    "colab_type": "code",
    "id": "NcufVLErvKCt",
    "outputId": "9d40c5d2-96dd-4e60-aca7-9c24f3ccc244"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Vocabulary(size=10)>\n",
      "10\n",
      "2\n",
      "bird\n"
     ]
    }
   ],
   "source": [
    "# Vocabulary instance\n",
    "category_vocab = Vocabulary(add_unk=False)\n",
    "for index, row in df.iterrows():\n",
    "    category_vocab.add_token(row.category)\n",
    "print (category_vocab) # __str__\n",
    "print (len(category_vocab)) # __len__\n",
    "index = category_vocab.lookup_token(\"bird\")\n",
    "print (index)\n",
    "print (category_vocab.lookup_index(index))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ubECmrcqZIHI"
   },
   "source": [
    "# Sequence vocbulary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "mUee35g37m8k"
   },
   "source": [
    "We will also create a vocabulary object for the actual images. It will store the mean and standard deviations for eahc image channel (RGB) which we will use later on for normalizing our images with the Vectorizer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "37pGFTBiZIbm"
   },
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "import string"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "gbIKSQ5a7jsG"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YvWL2JcgZPaw"
   },
   "outputs": [],
   "source": [
    "class SequenceVocabulary(Vocabulary):\n",
    "    def __init__(self, train_means, train_stds):\n",
    "        \n",
    "        self.train_means = train_means\n",
    "        self.train_stds = train_stds\n",
    "        \n",
    "    def to_serializable(self):\n",
    "        contents = {'train_means': self.train_means,\n",
    "                    'train_stds': self.train_stds}\n",
    "        return contents\n",
    "    \n",
    "    @classmethod\n",
    "    def from_dataframe(cls, df):\n",
    "        train_data = df[df.split == \"train\"]\n",
    "        means = {0:[], 1:[], 2:[]}\n",
    "        stds = {0:[], 1:[], 2:[]}\n",
    "        for image in train_data.image:\n",
    "            for dim in range(3):\n",
    "                means[dim].append(np.mean(image[:, :, dim]))\n",
    "                stds[dim].append(np.std(image[:, :, dim]))\n",
    "        train_means = np.array((np.mean(means[0]), np.mean(means[1]), \n",
    "                                np.mean(means[2])), dtype=\"float64\").tolist()\n",
    "        train_stds = np.array((np.mean(stds[0]), np.mean(stds[1]), \n",
    "                               np.mean(stds[2])), dtype=\"float64\").tolist()\n",
    "            \n",
    "        return cls(train_means, train_stds)\n",
    "        \n",
    "    def __str__(self):\n",
    "        return \"<SequenceVocabulary(train_means: {0}, train_stds: {1}>\".format(\n",
    "            self.train_means, self.train_stds)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "osFf9EGY71tT"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "-ODlh2wcahqH",
    "outputId": "df6e43f2-d6fb-41dd-c781-daa95565e07a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<SequenceVocabulary(train_means: [125.53776550292969, 123.14204406738281, 114.1255111694336], train_stds: [51.4995002746582, 50.7718505859375, 51.18278503417969]>\n"
     ]
    }
   ],
   "source": [
    "# Create SequenceVocabulary instance\n",
    "image_vocab = SequenceVocabulary.from_dataframe(split_df)\n",
    "print (image_vocab) # __str__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "lUZKa0c9YD0V"
   },
   "source": [
    "# Vectorizer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "-vlDb26u8BEy"
   },
   "source": [
    "The vectorizer will normalize our images using the vocabulary."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "DyfX-utj8JTz"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "RyxHZLTFX5VC"
   },
   "outputs": [],
   "source": [
    "class ImageVectorizer(object):\n",
    "    def __init__(self, image_vocab, category_vocab):\n",
    "        self.image_vocab = image_vocab\n",
    "        self.category_vocab = category_vocab\n",
    "\n",
    "    def vectorize(self, image):\n",
    "        # Avoid modifying the actual df\n",
    "        image = np.copy(image)\n",
    "        \n",
    "        # Normalize\n",
    "        for dim in range(3):\n",
    "            mean = self.image_vocab.train_means[dim]\n",
    "            std = self.image_vocab.train_stds[dim]\n",
    "            image[:, :, dim] = ((image[:, :, dim] - mean) / std)\n",
    "            \n",
    "        # Reshape from (32, 32, 3) to (3, 32, 32)\n",
    "        image = np.swapaxes(image, 0, 2)\n",
    "        image = np.swapaxes(image, 1, 2)\n",
    "                \n",
    "        return image\n",
    "    \n",
    "    @classmethod\n",
    "    def from_dataframe(cls, df):\n",
    "        # Create vocabularies\n",
    "        image_vocab = SequenceVocabulary.from_dataframe(df)\n",
    "        category_vocab = Vocabulary(add_unk=False)   \n",
    "        for category in sorted(set(df.category)):\n",
    "            category_vocab.add_token(category)\n",
    "        return cls(image_vocab, category_vocab)\n",
    "\n",
    "    @classmethod\n",
    "    def from_serializable(cls, contents):\n",
    "        image_vocab = SequenceVocabulary.from_serializable(contents['image_vocab'])\n",
    "        category_vocab = Vocabulary.from_serializable(contents['category_vocab'])\n",
    "        return cls(image_vocab=image_vocab, \n",
    "                   category_vocab=category_vocab)\n",
    "    \n",
    "    def to_serializable(self):\n",
    "        return {'image_vocab': self.image_vocab.to_serializable(),\n",
    "                'category_vocab': self.category_vocab.to_serializable()}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "kTxCnjAb8Kzn"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 85
    },
    "colab_type": "code",
    "id": "yXWIhtFUiDUe",
    "outputId": "07849d6e-b269-43ed-ad80-86e51d0b2dbd"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<SequenceVocabulary(train_means: [125.53776550292969, 123.14204406738281, 114.1255111694336], train_stds: [51.4995002746582, 50.7718505859375, 51.18278503417969]>\n",
      "<Vocabulary(size=10)>\n",
      "{'bird': 0, 'car': 1, 'cat': 2, 'deer': 3, 'dog': 4, 'frog': 5, 'horse': 6, 'plane': 7, 'ship': 8, 'truck': 9}\n",
      "(3, 32, 32)\n"
     ]
    }
   ],
   "source": [
    "# Vectorizer instance\n",
    "vectorizer = ImageVectorizer.from_dataframe(split_df)\n",
    "print (vectorizer.image_vocab)\n",
    "print (vectorizer.category_vocab)\n",
    "print (vectorizer.category_vocab.token_to_idx)\n",
    "image_vector = vectorizer.vectorize(split_df.iloc[0].image)\n",
    "print (image_vector.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Xm7s9RPThF3c"
   },
   "source": [
    "# Dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "22ptC-NZ9Ls1"
   },
   "source": [
    "The Dataset will create vectorized data from the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "2mL4eEdNX5c1"
   },
   "outputs": [],
   "source": [
    "import random\n",
    "from torch.utils.data import Dataset, DataLoader"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "TtnGYqxk9Hxc"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Dzegh16nX5fY"
   },
   "outputs": [],
   "source": [
    "class ImageDataset(Dataset):\n",
    "    def __init__(self, df, vectorizer, infer=False):\n",
    "        self.df = df\n",
    "        self.vectorizer = vectorizer\n",
    "        \n",
    "        # Data splits\n",
    "        if not infer:\n",
    "            self.train_df = self.df[self.df.split=='train']\n",
    "            self.train_size = len(self.train_df)\n",
    "            self.val_df = self.df[self.df.split=='val']\n",
    "            self.val_size = len(self.val_df)\n",
    "            self.test_df = self.df[self.df.split=='test']\n",
    "            self.test_size = len(self.test_df)\n",
    "            self.lookup_dict = {'train': (self.train_df, self.train_size), \n",
    "                                'val': (self.val_df, self.val_size),\n",
    "                                'test': (self.test_df, self.test_size)}\n",
    "            self.set_split('train')\n",
    "\n",
    "            # Class weights (for imbalances)\n",
    "            class_counts = df.category.value_counts().to_dict()\n",
    "            def sort_key(item):\n",
    "                return self.vectorizer.category_vocab.lookup_token(item[0])\n",
    "            sorted_counts = sorted(class_counts.items(), key=sort_key)\n",
    "            frequencies = [count for _, count in sorted_counts]\n",
    "            self.class_weights = 1.0 / torch.tensor(frequencies, dtype=torch.float32)\n",
    "        elif infer:\n",
    "            self.infer_df = self.df[self.df.split==\"infer\"]\n",
    "            self.infer_size = len(self.infer_df)\n",
    "            self.lookup_dict = {'infer': (self.infer_df, self.infer_size)}\n",
    "            self.set_split('infer')\n",
    "\n",
    "    @classmethod\n",
    "    def load_dataset_and_make_vectorizer(cls, df):\n",
    "        train_df = df[df.split=='train']\n",
    "        return cls(df, ImageVectorizer.from_dataframe(train_df))\n",
    "\n",
    "    @classmethod\n",
    "    def load_dataset_and_load_vectorizer(cls, df, vectorizer_filepath):\n",
    "        vectorizer = cls.load_vectorizer_only(vectorizer_filepath)\n",
    "        return cls(df, vectorizer)\n",
    "\n",
    "    def load_vectorizer_only(vectorizer_filepath):\n",
    "        with open(vectorizer_filepath) as fp:\n",
    "            return ImageVectorizer.from_serializable(json.load(fp))\n",
    "\n",
    "    def save_vectorizer(self, vectorizer_filepath):\n",
    "        with open(vectorizer_filepath, \"w\") as fp:\n",
    "            json.dump(self.vectorizer.to_serializable(), fp)\n",
    "\n",
    "    def set_split(self, split=\"train\"):\n",
    "        self.target_split = split\n",
    "        self.target_df, self.target_size = self.lookup_dict[split]\n",
    "\n",
    "    def __str__(self):\n",
    "        return \"<Dataset(split={0}, size={1})\".format(\n",
    "            self.target_split, self.target_size)\n",
    "\n",
    "    def __len__(self):\n",
    "        return self.target_size\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        row = self.target_df.iloc[index]\n",
    "        image_vector = self.vectorizer.vectorize(row.image)\n",
    "        category_index = self.vectorizer.category_vocab.lookup_token(row.category)\n",
    "        return {'image': image_vector, \n",
    "                'category': category_index}\n",
    "\n",
    "    def get_num_batches(self, batch_size):\n",
    "        return len(self) // batch_size\n",
    "\n",
    "    def generate_batches(self, batch_size, shuffle=True, drop_last=True, device=\"cpu\"):\n",
    "        dataloader = DataLoader(dataset=self, batch_size=batch_size, \n",
    "                                shuffle=shuffle, drop_last=drop_last)\n",
    "        for data_dict in dataloader:\n",
    "            out_data_dict = {}\n",
    "            for name, tensor in data_dict.items():\n",
    "                out_data_dict[name] = data_dict[name].to(device)\n",
    "            yield out_data_dict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "BMZbi-S29VQP"
   },
   "outputs": [],
   "source": [
    "def sample(dataset):\n",
    "    \"\"\"Some sanity checks on the dataset.\n",
    "    \"\"\"\n",
    "    sample_idx = random.randint(0,len(dataset))\n",
    "    sample = dataset[sample_idx]\n",
    "    print (\"\\n==> 🔢 Dataset:\")\n",
    "    print (\"Random sample: {0}\".format(sample))\n",
    "    print (\"Unvectorized category: {0}\".format(\n",
    "        dataset.vectorizer.category_vocab.lookup_index(sample['category'])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "DZKAha5f9VVG"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 51
    },
    "colab_type": "code",
    "id": "-sW6otUGX5iA",
    "outputId": "87911deb-a842-447c-8c58-5cef1591070a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([0.0002, 0.0002, 0.0002, 0.0002, 0.0002, 0.0002, 0.0002, 0.0002, 0.0002,\n",
      "        0.0002])\n"
     ]
    }
   ],
   "source": [
    "# Load dataset and vectorizer\n",
    "dataset = ImageDataset.load_dataset_and_make_vectorizer(split_df)\n",
    "dataset.save_vectorizer(config[\"vectorizer_file\"])\n",
    "vectorizer = dataset.vectorizer\n",
    "print (dataset.class_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "_TKZjOyM9liD"
   },
   "outputs": [],
   "source": [
    "# Sample checks\n",
    "sample(dataset=dataset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "SjPHp36i3G_i"
   },
   "source": [
    "# Model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "KPgY8JDi9_GO"
   },
   "source": [
    "Basic CNN architecture for image classification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "bPaf6Dy2X5ko"
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "OdZfoEKc-BBm"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "RKRPzX1nX5nN"
   },
   "outputs": [],
   "source": [
    "class ImageModel(nn.Module):\n",
    "    def __init__(self, num_hidden_units, num_classes, dropout_p):\n",
    "        super(ImageModel, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(3, 10, kernel_size=5) # input_channels:3, output_channels:10 (aka num filters)\n",
    "        self.conv2 = nn.Conv2d(10, 20, kernel_size=5) \n",
    "        self.conv_dropout = nn.Dropout2d(dropout_p)\n",
    "        self.fc1 = nn.Linear(20*5*5, num_hidden_units)\n",
    "        self.dropout = nn.Dropout(dropout_p)\n",
    "        self.fc2 = nn.Linear(num_hidden_units, num_classes)\n",
    "\n",
    "    def forward(self, x, apply_softmax=False):\n",
    "        # Conv pool\n",
    "        z = self.conv1(x) # (N, 10, 28, 28)\n",
    "        z = F.max_pool2d(z, 2) # (N, 10, 14, 14)\n",
    "        z = F.relu(z)\n",
    "        \n",
    "        # Conv pool\n",
    "        z = self.conv2(z) # (N, 20, 10, 10)\n",
    "        z = self.conv_dropout(z) \n",
    "        z = F.max_pool2d(z, 2) # (N, 20, 5, 5)\n",
    "        z = F.relu(z)\n",
    "        \n",
    "        # Flatten\n",
    "        z = z.view(-1, 20*5*5)\n",
    "        \n",
    "        # FC\n",
    "        z = F.relu(self.fc1(z))\n",
    "        z = self.dropout(z)\n",
    "        y_pred = self.fc2(z)\n",
    "        \n",
    "        if apply_softmax:\n",
    "            y_pred = F.softmax(y_pred, dim=1)\n",
    "        return y_pred "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "lMKxHPxs-JlA"
   },
   "outputs": [],
   "source": [
    "def initialize_model(config, vectorizer):\n",
    "    \"\"\"Initialize the model.\n",
    "    \"\"\"\n",
    "    print (\"\\n==> 🚀 Initializing model:\")\n",
    "    model = ImageModel(\n",
    "       num_hidden_units=config[\"fc\"][\"hidden_dim\"], \n",
    "       num_classes=len(vectorizer.category_vocab),\n",
    "       dropout_p=config[\"fc\"][\"dropout_p\"])\n",
    "    print (model.named_modules)\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "wSZOM8Xw-I8O"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 187
    },
    "colab_type": "code",
    "id": "z5C6DfY3-QXr",
    "outputId": "fd88fc88-41e7-4511-c08a-d40303d21bb8"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "==> 🚀 Initializing model:\n",
      "<bound method Module.named_modules of ImageModel(\n",
      "  (conv1): Conv2d(3, 10, kernel_size=(5, 5), stride=(1, 1))\n",
      "  (conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))\n",
      "  (conv_dropout): Dropout2d(p=0.1)\n",
      "  (fc1): Linear(in_features=500, out_features=100, bias=True)\n",
      "  (dropout): Dropout(p=0.1)\n",
      "  (fc2): Linear(in_features=100, out_features=10, bias=True)\n",
      ")>\n"
     ]
    }
   ],
   "source": [
    "# Initializing model\n",
    "model = initialize_model(config=config, vectorizer=vectorizer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "jAiIbY9TBGef"
   },
   "source": [
    "# Training"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "w5yLzB4XG5aw"
   },
   "source": [
    "Training operations for image classification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "bnMvjt9JX5p4"
   },
   "outputs": [],
   "source": [
    "import torch.optim as optim"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "kCkVhv54G6Ui"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "z8dxixyVHAi8"
   },
   "outputs": [],
   "source": [
    "def compute_accuracy(y_pred, y_target):\n",
    "    _, y_pred_indices = y_pred.max(dim=1)\n",
    "    n_correct = torch.eq(y_pred_indices, y_target).sum().item()\n",
    "    return n_correct / len(y_pred_indices) * 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "FhDr6F8-HAyf"
   },
   "outputs": [],
   "source": [
    "def update_train_state(model, train_state):\n",
    "    \"\"\" Update train state during training.\n",
    "    \"\"\"\n",
    "    # Verbose\n",
    "    print (\"[EPOCH]: {0} | [LR]: {1} | [TRAIN LOSS]: {2:.2f} | [TRAIN ACC]: {3:.1f}% | [VAL LOSS]: {4:.2f} | [VAL ACC]: {5:.1f}%\".format(\n",
    "      train_state['epoch_index'], train_state['learning_rate'], \n",
    "        train_state['train_loss'][-1], train_state['train_acc'][-1], \n",
    "        train_state['val_loss'][-1], train_state['val_acc'][-1]))\n",
    "\n",
    "    # Save one model at least\n",
    "    if train_state['epoch_index'] == 0:\n",
    "        torch.save(model.state_dict(), train_state['model_filename'])\n",
    "        train_state['stop_early'] = False\n",
    "\n",
    "    # Save model if performance improved\n",
    "    elif train_state['epoch_index'] >= 1:\n",
    "        loss_tm1, loss_t = train_state['val_loss'][-2:]\n",
    "\n",
    "        # If loss worsened\n",
    "        if loss_t >= train_state['early_stopping_best_val']:\n",
    "            # Update step\n",
    "            train_state['early_stopping_step'] += 1\n",
    "\n",
    "        # Loss decreased\n",
    "        else:\n",
    "            # Save the best model\n",
    "            if loss_t < train_state['early_stopping_best_val']:\n",
    "                torch.save(model.state_dict(), train_state['model_filename'])\n",
    "\n",
    "            # Reset early stopping step\n",
    "            train_state['early_stopping_step'] = 0\n",
    "\n",
    "        # Stop early ?\n",
    "        train_state['stop_early'] = train_state['early_stopping_step'] \\\n",
    "          >= train_state['early_stopping_criteria']\n",
    "    return train_state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Yw8Ek3fvHAwT"
   },
   "outputs": [],
   "source": [
    "class Trainer(object):\n",
    "    def __init__(self, dataset, model, model_file, device, shuffle, \n",
    "               num_epochs, batch_size, learning_rate, early_stopping_criteria):\n",
    "        self.dataset = dataset\n",
    "        self.class_weights = dataset.class_weights.to(device)\n",
    "        self.model = model.to(device)\n",
    "        self.device = device\n",
    "        self.shuffle = shuffle\n",
    "        self.num_epochs = num_epochs\n",
    "        self.batch_size = batch_size\n",
    "        self.loss_func = nn.CrossEntropyLoss(self.class_weights)\n",
    "        self.optimizer = optim.Adam(self.model.parameters(), lr=learning_rate)\n",
    "        self.scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n",
    "            optimizer=self.optimizer, mode='min', factor=0.5, patience=1)\n",
    "        self.train_state = {\n",
    "            'done_training': False,\n",
    "            'stop_early': False, \n",
    "            'early_stopping_step': 0,\n",
    "            'early_stopping_best_val': 1e8,\n",
    "            'early_stopping_criteria': early_stopping_criteria,\n",
    "            'learning_rate': learning_rate,\n",
    "            'epoch_index': 0,\n",
    "            'train_loss': [],\n",
    "            'train_acc': [],\n",
    "            'val_loss': [],\n",
    "            'val_acc': [],\n",
    "            'test_loss': -1,\n",
    "            'test_acc': -1,\n",
    "            'model_filename': model_file}\n",
    "  \n",
    "    def run_train_loop(self):\n",
    "        print (\"==> 🏋 Training:\")\n",
    "        for epoch_index in range(self.num_epochs):\n",
    "            self.train_state['epoch_index'] = epoch_index\n",
    "      \n",
    "            # Iterate over train dataset\n",
    "\n",
    "            # initialize batch generator, set loss and acc to 0, set train mode on\n",
    "            self.dataset.set_split('train')\n",
    "            batch_generator = self.dataset.generate_batches(\n",
    "                batch_size=self.batch_size, shuffle=self.shuffle, \n",
    "                device=self.device)\n",
    "            running_loss = 0.0\n",
    "            running_acc = 0.0\n",
    "            self.model.train()\n",
    "\n",
    "            for batch_index, batch_dict in enumerate(batch_generator):\n",
    "                # zero the gradients\n",
    "                self.optimizer.zero_grad()\n",
    "\n",
    "                # compute the output\n",
    "                y_pred = self.model(batch_dict['image'])\n",
    "\n",
    "                # compute the loss\n",
    "                loss = self.loss_func(y_pred, batch_dict['category'])\n",
    "                loss_t = loss.item()\n",
    "                running_loss += (loss_t - running_loss) / (batch_index + 1)\n",
    "\n",
    "                # compute gradients using loss\n",
    "                loss.backward()\n",
    "\n",
    "                # use optimizer to take a gradient step\n",
    "                self.optimizer.step()\n",
    "                \n",
    "                # compute the accuracy\n",
    "                acc_t = compute_accuracy(y_pred, batch_dict['category'])\n",
    "                running_acc += (acc_t - running_acc) / (batch_index + 1)\n",
    "\n",
    "            self.train_state['train_loss'].append(running_loss)\n",
    "            self.train_state['train_acc'].append(running_acc)\n",
    "\n",
    "            # Iterate over val dataset\n",
    "\n",
    "            # initialize batch generator, set loss and acc to 0; set eval mode on\n",
    "            self.dataset.set_split('val')\n",
    "            batch_generator = self.dataset.generate_batches(\n",
    "                batch_size=self.batch_size, shuffle=self.shuffle, device=self.device)\n",
    "            running_loss = 0.\n",
    "            running_acc = 0.\n",
    "            self.model.eval()\n",
    "\n",
    "            for batch_index, batch_dict in enumerate(batch_generator):\n",
    "\n",
    "                # compute the output\n",
    "                y_pred =  self.model(batch_dict['image'])\n",
    "\n",
    "                # compute the loss\n",
    "                loss = self.loss_func(y_pred, batch_dict['category'])\n",
    "                loss_t = loss.to(\"cpu\").item()\n",
    "                running_loss += (loss_t - running_loss) / (batch_index + 1)\n",
    "\n",
    "                # compute the accuracy\n",
    "                acc_t = compute_accuracy(y_pred, batch_dict['category'])\n",
    "                running_acc += (acc_t - running_acc) / (batch_index + 1)\n",
    "\n",
    "            self.train_state['val_loss'].append(running_loss)\n",
    "            self.train_state['val_acc'].append(running_acc)\n",
    "\n",
    "            self.train_state = update_train_state(model=self.model, train_state=self.train_state)\n",
    "            self.scheduler.step(self.train_state['val_loss'][-1])\n",
    "            if self.train_state['stop_early']:\n",
    "                break\n",
    "          \n",
    "    def run_test_loop(self):\n",
    "        # initialize batch generator, set loss and acc to 0; set eval mode on\n",
    "        self.dataset.set_split('test')\n",
    "        batch_generator = self.dataset.generate_batches(\n",
    "            batch_size=self.batch_size, shuffle=self.shuffle, device=self.device)\n",
    "        running_loss = 0.0\n",
    "        running_acc = 0.0\n",
    "        self.model.eval()\n",
    "\n",
    "        for batch_index, batch_dict in enumerate(batch_generator):\n",
    "            # compute the output\n",
    "            y_pred =  self.model(batch_dict['image'])\n",
    "\n",
    "            # compute the loss\n",
    "            loss = self.loss_func(y_pred, batch_dict['category'])\n",
    "            loss_t = loss.item()\n",
    "            running_loss += (loss_t - running_loss) / (batch_index + 1)\n",
    "\n",
    "            # compute the accuracy\n",
    "            acc_t = compute_accuracy(y_pred, batch_dict['category'])\n",
    "            running_acc += (acc_t - running_acc) / (batch_index + 1)\n",
    "\n",
    "        self.train_state['test_loss'] = running_loss\n",
    "        self.train_state['test_acc'] = running_acc\n",
    "        \n",
    "        # Verbose\n",
    "        print (\"==> 💯 Test performance:\")\n",
    "        print (\"Test loss: {0:.2f}\".format(self.train_state['test_loss']))\n",
    "        print (\"Test Accuracy: {0:.1f}%\".format(self.train_state['test_acc']))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "MVqHg8rqHAt5"
   },
   "outputs": [],
   "source": [
    "def plot_performance(train_state, save_dir, show_plot=True):\n",
    "    \"\"\" Plot loss and accuracy.\n",
    "    \"\"\"\n",
    "    # Figure size\n",
    "    plt.figure(figsize=(15,5))\n",
    "\n",
    "    # Plot Loss\n",
    "    plt.subplot(1, 2, 1)\n",
    "    plt.title(\"Loss\")\n",
    "    plt.plot(train_state[\"train_loss\"], label=\"train\")\n",
    "    plt.plot(train_state[\"val_loss\"], label=\"val\")\n",
    "    plt.legend(loc='upper right')\n",
    "\n",
    "    # Plot Accuracy\n",
    "    plt.subplot(1, 2, 2)\n",
    "    plt.title(\"Accuracy\")\n",
    "    plt.plot(train_state[\"train_acc\"], label=\"train\")\n",
    "    plt.plot(train_state[\"val_acc\"], label=\"val\")\n",
    "    plt.legend(loc='lower right')\n",
    "\n",
    "    # Save figure\n",
    "    plt.savefig(os.path.join(save_dir, \"performance.png\"))\n",
    "\n",
    "    # Show plots\n",
    "    if show_plot:\n",
    "        print (\"==> 📈 Metric plots:\")\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "H_dN8IRRHArg"
   },
   "outputs": [],
   "source": [
    "def save_train_state(train_state, save_dir):\n",
    "    train_state[\"done_training\"] = True\n",
    "    with open(os.path.join(save_dir, \"train_state.json\"), \"w\") as fp:\n",
    "        json.dump(train_state, fp)\n",
    "    print (\"==> ✅ Training complete!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7yLIizWvI-KV"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 119
    },
    "colab_type": "code",
    "id": "EtJhoyL2HApb",
    "outputId": "732330ab-2990-4c03-84e5-59b278155ccd"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 🏋 Training:\n",
      "[EPOCH]: 0 | [LR]: 0.001 | [TRAIN LOSS]: 1.70 | [TRAIN ACC]: 38.3% | [VAL LOSS]: 1.44 | [VAL ACC]: 48.2%\n",
      "[EPOCH]: 1 | [LR]: 0.001 | [TRAIN LOSS]: 1.39 | [TRAIN ACC]: 50.2% | [VAL LOSS]: 1.31 | [VAL ACC]: 52.6%\n",
      "[EPOCH]: 2 | [LR]: 0.001 | [TRAIN LOSS]: 1.28 | [TRAIN ACC]: 54.6% | [VAL LOSS]: 1.20 | [VAL ACC]: 57.7%\n",
      "[EPOCH]: 3 | [LR]: 0.001 | [TRAIN LOSS]: 1.19 | [TRAIN ACC]: 58.0% | [VAL LOSS]: 1.12 | [VAL ACC]: 60.3%\n",
      "[EPOCH]: 4 | [LR]: 0.001 | [TRAIN LOSS]: 1.13 | [TRAIN ACC]: 60.2% | [VAL LOSS]: 1.09 | [VAL ACC]: 61.2%\n"
     ]
    }
   ],
   "source": [
    "# Training\n",
    "trainer = Trainer(\n",
    "    dataset=dataset, model=model, model_file=config[\"model_file\"],\n",
    "    device=config[\"device\"], shuffle=config[\"shuffle\"], \n",
    "    num_epochs=config[\"num_epochs\"], batch_size=config[\"batch_size\"], \n",
    "    learning_rate=config[\"learning_rate\"], \n",
    "    early_stopping_criteria=config[\"early_stopping_criteria\"])\n",
    "trainer.run_train_loop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 352
    },
    "colab_type": "code",
    "id": "2G6I5YWtt_Ea",
    "outputId": "c3fc5823-0f0a-43cc-d3fc-9cd067852734"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 📈 Metric plots:\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2gAAAE+CAYAAAD4XjP+AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzs3Xd41HW6///nTCaT3jPpvZEGAQII\nSE3AUIJUUVEs4O7xu+u6u27RLa6FLfrbdc8e17NnL1HRxS69SBEkNOk9CekJKZBk0nsmmZnfH8Eo\nStOUTya5H9flJZn6YoBk7nnf7/utMpvNZoQQQgghhBBCKE6tdAAhhBBCCCGEEF2kQBNCCCGEEEKI\nAUIKNCGEEEIIIYQYIKRAE0IIIYQQQogBQgo0IYQQQgghhBggpEATQgghhBBCiAFCCjQhvqdhw4ZR\nXl6udAwhhBCiX9x3333cfffdSscQYtCTAk0IIYQQQtxUTk4OTk5O+Pn5cebMGaXjCDGoSYEmRC9r\nb2/nD3/4AykpKcyePZuXXnoJo9EIwLvvvsvs2bOZNWsWS5YsITc396aXCyGEEAPBxo0bmTVrFqmp\nqWzatKn78k2bNpGSkkJKSgq/+tWvMBgMN7z82LFjzJw5s/u+X//6n//8J7///e9ZsmQJb7/9NiaT\niRdeeIGUlBSSkpL41a9+RUdHBwA1NTU8/vjjJCcnM2/ePA4dOkRaWhqpqanXZF60aBF79uzp65dG\niF6nUTqAEIPNO++8Q3l5Odu3b6ezs5MHH3yQbdu2kZyczP/8z/+wb98+HB0d2bFjB2lpafj6+l73\n8sjISKV/K0IIIQRGo5HPPvuMH//4x1hZWfHKK69gMBiorKzk5ZdfZtOmTXh5efGTn/yE//znP8ya\nNeu6lw8fPvymz7N//342b96Mu7s7u3bt4uTJk2zbtg2TycTChQv59NNPmT9/Pq+88grh4eH8+9//\nJjMzk0cffZSDBw+i1+vJysoiOjqay5cvU1xczJQpU/rpVRKi90iBJkQvS0tLY8WKFWg0GjQaDfPm\nzePw4cPMmTMHlUrFunXrSE1NZfbs2QB0dHRc93IhhBBiIDh06BDDhw/H0dERgHHjxrFv3z7q6uoY\nNWoU3t7eALzyyitYWVmxfv36615+6tSpmz5PQkIC7u7uAKSkpDB9+nSsra0BGD58OCUlJUBXIbd6\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7feff2c47cf8>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot performance\n",
    "plot_performance(train_state=trainer.train_state, \n",
    "                 save_dir=config[\"save_dir\"], show_plot=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 68
    },
    "colab_type": "code",
    "id": "Iz3G5eaTS04m",
    "outputId": "fbb86f93-da9f-4691-cc83-98bfd5a04c13"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 💯 Test performance:\n",
      "Test loss: 1.10\n",
      "Test Accuracy: 61.2%\n"
     ]
    }
   ],
   "source": [
    "# Test performance\n",
    "trainer.run_test_loop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "kqMzljfpS09F",
    "outputId": "4a2523aa-c54b-4fee-8f66-37acb5577457"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> ✅ Training complete!\n"
     ]
    }
   ],
   "source": [
    "# Save all results\n",
    "save_train_state(train_state=trainer.train_state, save_dir=config[\"save_dir\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1fMNOVJUYvhs"
   },
   "source": [
    "~60% test performance for our CIFAR10 dataset is not bad but we can do way better."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "P9DcE8tHYvfX"
   },
   "source": [
    "# Transfer learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "EclYytw6Swh-"
   },
   "source": [
    "In this section, we're going to use a pretrained model that performs very well on a different dataset. We're going to take the architecture and the initial convolutional weights from the model to use on our data. We will freeze the initial convolutional weights and fine tune the later convolutional and fully-connected layers. \n",
    "\n",
    "Transfer learning works here because the initial convolution layers act as excellent feature extractors for common spatial features that are shared across images regardless of their class. We're going to leverage these large, pretrained models' feature extractors for our own dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "D8w9CEDeONTY"
   },
   "outputs": [],
   "source": [
    "!pip install torchvision"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "mxl4PEfqTMwm"
   },
   "outputs": [],
   "source": [
    "from torchvision import models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 54
    },
    "colab_type": "code",
    "id": "GjufXPDJTB7W",
    "outputId": "7eb92456-10c4-416d-a658-7db3d5e9ad38"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['alexnet', 'densenet121', 'densenet161', 'densenet169', 'densenet201', 'inception_v3', 'resnet101', 'resnet152', 'resnet18', 'resnet34', 'resnet50', 'squeezenet1_0', 'squeezenet1_1', 'vgg11', 'vgg11_bn', 'vgg13', 'vgg13_bn', 'vgg16', 'vgg16_bn', 'vgg19', 'vgg19_bn']\n"
     ]
    }
   ],
   "source": [
    "model_names = sorted(name for name in models.__dict__\n",
    "    if name.islower() and not name.startswith(\"__\")\n",
    "    and callable(models.__dict__[name]))\n",
    "print (model_names)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "daJN4BSWS016"
   },
   "outputs": [],
   "source": [
    "model_name = 'vgg19_bn'\n",
    "vgg_19bn = models.__dict__[model_name](pretrained=True) # Set false to train from scratch\n",
    "print (vgg_19bn.named_parameters)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "XBudDGFz1j87"
   },
   "source": [
    "The VGG model we chose has a `features` and a `classifier` component. The `features` component is composed of convolution and pooling layers which act as feature extractors. The `classifier` component is composed on fully connected layers. We're going to freeze most of the `feature` component and design our own FC layers for our CIFAR10 task. You can access the default code for all models at `/usr/local/lib/python3.6/dist-packages/torchvision/models` if you prefer cloning and modifying that instead."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "5rz1g7FrOlQg"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "YmzQIXsd59Rj"
   },
   "outputs": [],
   "source": [
    "class ImageModel(nn.Module):\n",
    "    def __init__(self, feature_extractor, num_hidden_units, \n",
    "                 num_classes, dropout_p):\n",
    "        super(ImageModel, self).__init__()\n",
    "        \n",
    "        # Pretrained feature extractor\n",
    "        self.feature_extractor = feature_extractor\n",
    "        \n",
    "        # FC weights\n",
    "        self.classifier = nn.Sequential(\n",
    "            nn.Linear(512, 250, bias=True),\n",
    "            nn.ReLU(),\n",
    "            nn.Dropout(0.5),\n",
    "            nn.Linear(250, 100, bias=True),\n",
    "            nn.ReLU(),\n",
    "            nn.Dropout(0.5),\n",
    "            nn.Linear(100, 10, bias=True),\n",
    "            )\n",
    "\n",
    "    def forward(self, x, apply_softmax=False):\n",
    "          \n",
    "        # Feature extractor\n",
    "        z = self.feature_extractor(x)\n",
    "        z = z.view(x.size(0), -1)\n",
    "        \n",
    "        # FC\n",
    "        y_pred = self.classifier(z)\n",
    "\n",
    "        if apply_softmax:\n",
    "            y_pred = F.softmax(y_pred, dim=1)\n",
    "        return y_pred "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "0uVyLE91OnFq"
   },
   "outputs": [],
   "source": [
    "def initialize_model(config, vectorizer, feature_extractor):\n",
    "    \"\"\"Initialize the model.\n",
    "    \"\"\"\n",
    "    print (\"\\n==> 🚀 Initializing model:\")\n",
    "    model = ImageModel(\n",
    "       feature_extractor=feature_extractor,\n",
    "       num_hidden_units=config[\"fc\"][\"hidden_dim\"], \n",
    "       num_classes=len(vectorizer.category_vocab),\n",
    "       dropout_p=config[\"fc\"][\"dropout_p\"])\n",
    "    print (model.named_modules)\n",
    "    return model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "C75F1xbdOnfP"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "czo1bGBwXKNj"
   },
   "outputs": [],
   "source": [
    "# Initializing model\n",
    "model = initialize_model(config=config, vectorizer=vectorizer, \n",
    "                         feature_extractor=vgg_19bn.features)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "hZybxGHoDTwQ"
   },
   "outputs": [],
   "source": [
    "# Finetune last few conv layers and FC layers\n",
    "for i, param in enumerate(model.feature_extractor.parameters()):\n",
    "    if i < 36:\n",
    "        param.requires_grad = False\n",
    "    else:\n",
    "        param.requires_grad = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 119
    },
    "colab_type": "code",
    "id": "GTbYKussTvB2",
    "outputId": "04e2b9cf-0870-4aac-ec10-fcdf5b8b34a3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 🏋 Training:\n",
      "[EPOCH]: 0 | [LR]: 0.001 | [TRAIN LOSS]: 0.95 | [TRAIN ACC]: 70.9% | [VAL LOSS]: 0.62 | [VAL ACC]: 79.8%\n",
      "[EPOCH]: 1 | [LR]: 0.001 | [TRAIN LOSS]: 0.57 | [TRAIN ACC]: 83.1% | [VAL LOSS]: 0.54 | [VAL ACC]: 83.0%\n",
      "[EPOCH]: 2 | [LR]: 0.001 | [TRAIN LOSS]: 0.43 | [TRAIN ACC]: 87.2% | [VAL LOSS]: 0.49 | [VAL ACC]: 84.6%\n",
      "[EPOCH]: 3 | [LR]: 0.001 | [TRAIN LOSS]: 0.33 | [TRAIN ACC]: 90.3% | [VAL LOSS]: 0.57 | [VAL ACC]: 83.5%\n",
      "[EPOCH]: 4 | [LR]: 0.001 | [TRAIN LOSS]: 0.27 | [TRAIN ACC]: 92.4% | [VAL LOSS]: 0.53 | [VAL ACC]: 85.3%\n"
     ]
    }
   ],
   "source": [
    "# Training\n",
    "trainer = Trainer(\n",
    "    dataset=dataset, model=model, model_file=config[\"model_file\"],\n",
    "    device=config[\"device\"], shuffle=config[\"shuffle\"], \n",
    "    num_epochs=config[\"num_epochs\"], batch_size=config[\"batch_size\"], \n",
    "    learning_rate=config[\"learning_rate\"], \n",
    "    early_stopping_criteria=config[\"early_stopping_criteria\"])\n",
    "trainer.run_train_loop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 352
    },
    "colab_type": "code",
    "id": "NCLCnQgATvMj",
    "outputId": "c27278e4-3cf1-4401-ff60-03f0a8b4c3cb"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 📈 Metric plots:\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2gAAAE+CAYAAAD4XjP+AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzs3Xd41PeZ7v/3FPVeZtQ7SAIBRgJk\nmimiG9txi40d2zh4T87ucbLnbBIf9kc2IXY2jnM2zkns3exJsk6MSxxcwD1gY0THNGGMBBJCSKig\nXlFDZeb3h2AAm46k0Uj367p8WTPf0czNgEbzzPf5PB+D3W63IyIiIiIiIk5ndHYAERERERER6aMC\nTUREREREZIhQgSYiIiIiIjJEqEATEREREREZIlSgiYiIiIiIDBEq0ERERERERIYIFWgiNyglJYWq\nqipnxxARERkUy5Yt46677nJ2DJFhTwWaiIiIiFzRsWPH8PPzIzIykoMHDzo7jsiwpgJNpJ+dOXOG\nn/zkJyxatIglS5bw3HPP0dvbC8Brr73GkiVLWLx4Mffffz+FhYVXvF5ERGQoWL9+PYsXL+aOO+7g\n3XffdVz/7rvvsmjRIhYtWsRTTz1FV1fXZa/fs2cPCxYscHzvhZdffPFF/uVf/oX777+fl19+GZvN\nxtNPP82iRYvIysriqaeeoru7G4CGhgb+/u//nnnz5nHnnXeyY8cOtmzZwh133HFR5nvvvZdNmzYN\n9FMj0u/Mzg4gMtysWbOGqqoqPvroI3p6enjkkUf48MMPmTdvHr/97W/Jzs7G19eXv/3tb2zZsoWI\niIhLXj969Ghn/1FERETo7e3l008/5cknn8RkMvH888/T1dVFTU0Nv/zlL3n33XexWq1873vf45VX\nXmHx4sWXvH78+PFXfJytW7fy3nvvERwczMaNG9m/fz8ffvghNpuNe+65h48//phvfOMbPP/88yQl\nJfH//t//48iRI3z7299m+/bt1NbWkp+fT2pqKqdOnaK0tJRZs2YN0rMk0n9UoIn0sy1btrBixQrM\nZjNms5k777yTnTt3cvvtt2MwGHj77be54447WLJkCQDd3d2XvF5ERGQo2LFjB+PHj8fX1xeAzMxM\nsrOzaWpqIj09nbCwMACef/55TCYT77zzziWvP3DgwBUf55ZbbiE4OBiARYsWMXfuXNzc3AAYP348\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7feff31896a0>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot performance\n",
    "plot_performance(train_state=trainer.train_state, \n",
    "                 save_dir=config[\"save_dir\"], show_plot=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 68
    },
    "colab_type": "code",
    "id": "Hjn0HJVoTvJ0",
    "outputId": "92d8644d-f51a-4d82-bff0-78aba33131c3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> 💯 Test performance:\n",
      "Test loss: 0.52\n",
      "Test Accuracy: 85.7%\n"
     ]
    }
   ],
   "source": [
    "# Test performance\n",
    "trainer.run_test_loop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 34
    },
    "colab_type": "code",
    "id": "ZQVrGTNNTvH0",
    "outputId": "44a24c37-2e22-47af-974d-3fc197b6427a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> ✅ Training complete!\n"
     ]
    }
   ],
   "source": [
    "# Save all results\n",
    "save_train_state(train_state=trainer.train_state, save_dir=config[\"save_dir\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7CL689FebJhf"
   },
   "source": [
    "Much better performance! If you let it train long enough, we'll actually reach ~95% accuracy :)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "02iDXCtiYo5K"
   },
   "source": [
    "## Inference"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "cVT--tAvnOu7"
   },
   "outputs": [],
   "source": [
    "from pylab import rcParams\n",
    "rcParams['figure.figsize'] = 2, 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "KFEGIgA1NKJ-"
   },
   "source": [
    "### Components"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "5IH9qjDpNW8m"
   },
   "outputs": [],
   "source": [
    "class Inference(object):\n",
    "    def __init__(self, model, vectorizer, device=\"cpu\"):\n",
    "        self.model = model.to(device)\n",
    "        self.vectorizer = vectorizer\n",
    "        self.device = device\n",
    "  \n",
    "    def predict_category(self, dataset):\n",
    "        # Batch generator\n",
    "        batch_generator = dataset.generate_batches(\n",
    "            batch_size=len(dataset), shuffle=False, device=self.device)\n",
    "        self.model.eval()\n",
    "        \n",
    "        # Predict\n",
    "        for batch_index, batch_dict in enumerate(batch_generator):\n",
    "            # compute the output\n",
    "            y_pred =  self.model(batch_dict['image'], apply_softmax=True)\n",
    "\n",
    "            # Top k categories\n",
    "            y_prob, indices = torch.topk(y_pred, k=len(self.vectorizer.category_vocab))\n",
    "            probabilities = y_prob.detach().to('cpu').numpy()[0]\n",
    "            indices = indices.detach().to('cpu').numpy()[0]\n",
    "\n",
    "            results = []\n",
    "            for probability, index in zip(probabilities, indices):\n",
    "                category = self.vectorizer.category_vocab.lookup_index(index)\n",
    "                results.append({'category': category, 'probability': probability})\n",
    "\n",
    "        return results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "uFJ7Dmn4NLm7"
   },
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "OtKJsv4HNLsN"
   },
   "outputs": [],
   "source": [
    "# Load vectorizer\n",
    "with open(config[\"vectorizer_file\"]) as fp:\n",
    "    vectorizer = ImageVectorizer.from_serializable(json.load(fp))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "iAeHP-FvP26o"
   },
   "outputs": [],
   "source": [
    "# Load the model\n",
    "model = initialize_model(config=config, vectorizer=vectorizer, feature_extractor=vgg_19bn.features)\n",
    "model.load_state_dict(torch.load(config[\"model_file\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "OlOlBXp-mWYT"
   },
   "outputs": [],
   "source": [
    "# Initialize\n",
    "inference = Inference(model=model, vectorizer=vectorizer, device=config[\"device\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 176
    },
    "colab_type": "code",
    "id": "6NwjskFsysKb",
    "outputId": "00e7d3c2-b011-4e62-df7c-8e288040155a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Actual: bird\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAI0AAACOCAYAAAAMyosLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAEWBJREFUeJztXUmPHMl1jszK2teu6pVsshfOiBoN\nBGm0+GDooIthQRCkm36bffTvMHywLcOADxagAWcRNeSQzWb1WkvXllm56Zbv+0JuuePAMgy87xSF\niI6KzH4Vb3/Py/M8NwqFA/z/6wMo/v9BiUbhDCUahTOUaBTOUKJROEOJRuEMJRqFM5RoFM4INvEl\n//SP/1CMz/70Oc0dH50U48VqSXO//+//KsYvXvyhGF9cDmldnmfF2Pc9mvPgs+/Jb6Rcq9G63U/+\nthg/fvYrmjt48h2ZO+gX461amdbNp+fFeBXys+wMHhfjwO/QXKMp++T5uhhfvF/Ruk5Pzv/97z+i\nuek0KsYvvrwsxr7XpXWpkf3TdEpz24NKMf7tb/7G3Ae9aRTOUKJROGMj7On45LgYp8tbmisFAax7\nRnPfvHpVjJdhKntkTOtpJu4z33Kl+bmwp1JJxtk6pnXz0aQYV095/+1W538cdxoVWrfVqxfj4fCC\n5pKsVIzbbWYZ9ar83e1I/s73GrSuWhE2Fq4ymotC+RyUZN1kOqd1na5813rNe6xWa/MQ6E2jcIYS\njcIZSjQKZ2xEpmm1WsU4TVKaOzs7K8aVWovm9vZATS2Lipzmtkwje2aWTONlGcyBTBPzOaZjUT+r\nAavStUpV/m6dFONZyjJAqysyyO7uEc2dn78vxuHykua8VM5crsp52+1tWtdsynNHUURzURTK/pGo\n+0HAclenK++xv71Lc+FibB4CvWkUzlCiUThjI+wpAxaxjvlKX8xFJby8uqG57pZcn93eTjF+P+Tr\nPUlBfWaDsPHhZ5GC5dh4vLBUFpZUrlV5DlRdE8iGlRpf/ZmRdaPxwjqjqNxxxNbiZlXOcnx6KOdN\n+N9TqQsLarbYoj1byJ71uuxXrTdpXe7LulLArPzg8cA8BHrTKJyhRKNwhhKNwhkbkWmSRGSJd5Y8\nMp2I+X4FfN8YY5ptMdlPZrNivAhZLsoNuBF4C3IrgJPbZIZN6P09kZ9aW2zmv7gdFeNoLa/s5Ogx\nrbu8vC7G11d3NNfbahfj7R1WpQc9kYWabZFHopDVauOJ/De94/3RRdLfFtXfD/hfHEZiWmh32MRx\nsN83D4HeNApnKNEonLER9hSCxXO0YkvsuxthT94Ve8DLZTne9VjU8TDnPYwHFlU7X/Sen4VXZquv\nV5ErPbKsykEu50hT+e5kzewjAP1+f79Nc9vbwvKabVbpK1XZM85lzzRltT3wZd35+VuaCyOxVA/2\nRW1v9ZgFHTYOinG9xv/+0gOpQW8ahTOUaBTO2Ah7uh2LI8yznIFfvXxZjNs1Djo6PBDt5Pj442Ic\n56wijSei3Xg+a0VkBQb2cfjoKa3b35fPseUM7LTkzN22nHEdcQzvoC+soGuxhUZTWFK1xudfRegs\nFQtu5PGzvH0NQWkr/u4vvvq6GJ/Gwqp+8uiA1lXBou0ZZsOZeVgtCL1pFM5QolE4Q4lG4YyNyDTj\nkcg0sRXQ3ev1ivGgxV7Wn//s74oxepr/7T9+R+uubiQYu9NhdTZKxHoMMVhmywpACkryd0HGMk0z\nkM/1ag3+hp/F98ULbXKW3W6uRO5qNK2A9L7IMZi2FVs6cB2C2mdWjtgTyB/rdmVdapkFMvDupzl7\n+qdzka2eP94x90FvGoUzlGgUztgIe3p8KKrzZz/6Ec39+KefyQfWIs13T79bjAMIjNrd26d1dzO5\nVrt9DjrySqLerhNhJ1HK6uwyknygvZ1DmmvWRc2uNITt1OscCAUGbBPH/DAepNualNlCAqp7UBHW\n5fmWxbYmZ6xbuVNPT07hvHIuz7D1PFyKlXk0Y6dne/9+loTQm0bhDCUahTOUaBTO2IhM88knIps8\nfcqBS14JgqSs/OQcPOIoBhw8+iGti0E+SS1TuBeAyR7UzdiymI+gVEenzq+l1xZZYryUMwUVK2gM\nAs3n85DmAgP5Rn2WR3Io/zGeitffBCwzxfASFiGr+wNQnysgXM0mVu487HF5wQFx9V0NwlJ8ICjR\nKJyxEfZU8kVd9i292ksTmGNVtNIUVTeEfKnlkq2hObCdxE7LTYSFlCryuJnHv5c6VLVKEj6jXxK2\nU6vJ/lnO7DReyxkTK7+r1pB3sFoz64rBaj25m8M6rlRVa4rn3PZIX15fFePASAxyuODvapRBbW+y\necKUrADre6A3jcIZSjQKZ2yEPaUJOMkitlCWMvzMNOxB+kUdqk5VrWKMeFXHGWsV6AH0y8ieeI/h\nUKo6YJUIY4yJc9FislRY4zri71pBhS5MkzXGmKAubDixrNE5VMkq1yS2uFRlFlQGa3F/h623aOld\nQIWrOGaWk0N8dbXJccxeiR2p90FvGoUzlGgUzlCiUThjIzJNGVjlYGeLJzPg9ZaVE9N5/QBkk4D5\nNOYiZZk9J3JBDqm4F6CiGmPM7/7zX4vxL/7+l3yODFT6VF5ZXuLfXAwlVUylTnPzSNRqz1L3c/i7\nKpgFjGU+uIOyLAvIczLGmPkygXUS8JUnLD8NGnKuToMD+UeTmXkI9KZROEOJRuGMjbAnD/J3rLBU\ncjbmlkUS50KIiU0tS2wIxRPD0MrlAZUbuIz5/edf0rpWT6yjB4d7NPfmW1HH5zOxsC5CthyXqsKH\no8TOv8Ii2PzaE1DdS8B6kW0ZY8zkTtjHZMnfvQL2Z7M1xBwCuTrWHuNEnu3XP/nevXvoTaNwhhKN\nwhlKNApnbESmyYGfzyyvawhFoMs+5wrFkLIToSvCovVFJDzcSqsyHuQuL6BA8/BqQuu+85EEh13d\ncuDSi6++KMbLhRzq8AnnST+GalqJpfpfXEnul51vhB738K80tYggRztLWG6pYI48mALilN02OQSr\nr0Keu7Gqa90HvWkUzlCiUThjQ15uuaqvbrj/0ArYU7q2POBI03Dl5iW+3sO1XNXTGVePQitqAqr6\n6zfvaZ0PnvJ63S4TIpbTk2fS8+D0KedfNTAPymeLcBU89i9fn9NcGGExSSgFYpknaqAud5t8RlT/\nIyjGXS5bgW3YY8IyC2TZw+4QvWkUzlCiUThjQ+xJ2M6F1f9gAVdkxePjVKEocArxw/MVa2BraEcY\nxlabPtA40lzG16MRrcuNzH38/COae/6RpLzuQcHFwHJYriHW1/PZobi7IwFP1zfMuq6upftvDuzD\nL3NQVAyW43nCQV7oA8U46dCOVTayR5zwGePYUj3vgd40Cmco0SicoUSjcMZGZJoAAqgaLebnq6VY\nWNdWEFa1hOqnjFeWTIOx6mvrkTJoXYhlPJpWzs8yElW9s9WhuQOw9DZhe6tDscnhjGnOMkcZLLaf\nfsKtpKczUZe/fCvBYakl4y2hUlVqySPdrshaCciQuV1I25N3l1vW4pKt498DvWkUzlCiUThjQ0FY\nQpuZFViEal6e8pWbe1iwGVVRPjaGDGdWJ10DVzy2t9nbY2djDuzk6CkXpm5Ci5t8LWysbH2XD/HI\nqVWoMQN1uVrlahDHJ4+K8etrMQVcjzlmNwYPru/x/jlYc7Fzru/zGSeTO5hjdhRlVs+Je6A3jcIZ\nSjQKZyjRKJyxmVIjEDBuyzTUftlqZDEGF0AAgdOzBXvK0XOwWrPZvAxFq8tgln/27Dmtm06kgPV4\nzPtv9yVXqwp53laHYpIz/JRdAKnB3CyWHZrQbOMOKmHNZmxaMBBoH1jyyAoKZJcgmC0MeY80hffj\n8QPE1v/mPuhNo3CGEo3CGRthT6jatVocPLSG8hZzO10VrZ7A4podttiugK2trKLPqxDSfsGjXq+x\nRbhWlh4NX33xhvdYiro82BZv9clTLlI9n4vaHq/56kd1v9myWiF6sn8AxRnLVh5YDQpwG6slI8YW\nI/tLLZbjwf8itCtyZdrvSfGBoESjcIYSjcIZG5JphDe321bJLqh20bRKX1wPJaIN85daLZZHun2R\nR7oDlnfGt+IZjkLIDQ9ZNQ982XM8YtV/cid9NustkcGCCstnr/4kbY8vhmOaa3dEVvnshx/THJ4l\nXovM4RmOCODWTXz+DOQRz5P3Xa2y6o/lVmJLLkpTdSMoPhCUaBTO2Ax7wi/0rQLHYPVtVvg4KfQk\nmII62+tzZcuLG0mjvbMCtJYr8UrHkFdVqbCnOYTApTizrMrgKY9TeZrzCw5OjyHddnh9TXPDawj2\ntvKNRpAOewFebuNZ1TZBI/b+4ucuk5WqqOZxzCwny8HbXuH9G1b76/ugN43CGUo0CmdshD1hz4Ar\nq13MJfQC2AYtyBhjKmW5ZlstYWujMWsmITgzw9DqdAvpq1cL+e7JlPfAylWBdU33G9LSpgkW7bN3\nQ1qHLK/T42dBxWR4xdUZ3t9IXHCSoWOTU4x9sBD7VpBXBQK71mBJt3OZkrVYzHPLcer5GiOs+EBQ\nolE4Q4lG4YzN5HJD8NB8zsHS06nw9zhhHhuDOp6AOvtuyHJRqyM5P/WanSctqm+3I8FUtSpbn69G\nIuNkVq7QxYUEaEXgUY+tnoZYoiS3ynZgGY+1VcWqCiryX7PK4rGS1GrmsUI5Bnpo2c1HQNv3rbyq\nvyi9eg/0plE4Q4lG4YwN5T1h4WW2CGNQ1tx2IoLKjQFOnf42rXv16ttivL8zoLmba7EWX0OZk709\nLjCNaa5+ye6RBPlMwD5iq29TCsWcw5BzuFZLKENiGcUTcD7i/nbOEr5HjH02xpgAKm3VanKuxGL5\nKZRA8aw7I9W8J8WHghKNwhlKNApnbFymySw5ANXNUpl7R9bqohajTNOos1rdhB5GoxG3JT45lrIe\nb8/OivFwyP2e+jviKkisnPI1uEG2tkRtfwequDHGBJBU3h1YAfSpeK/tfk/Y47NSwXbO7IlH2SrJ\n+Iwoj2BTjlqd32kDPscRRwQsrAYb90FvGoUzlGgUzthQJSz5mumMPbwRpLJ2oJqTMcbEEEC1OxCv\nsV1g+gefSm+ily9f0Vw5kOv+9OQU1v2R1o3Hoprv7LI6vrUlrCsjNsC/ue6WsKR2i59lMhX2tFjw\n+RPIK8YY6q0t9pRjCnMUszc/BEt1GUqxIPs3xph6TeaWVpxxFGvek+IDQYlG4YyNsKcaVEXo9dli\n++LVu2Lc3eK5t2++LcaDbelDsF6ys85rQOHoY2YtL7+R/Xvbwmaef8rVrs7O5LsWM9bA0BF5Oxat\n6/aaW/ysoGPtTY33SCGGt9dj1lVJIVU2ETYzHrN2hsWiS5azEVscJlBkO4q4YOToRt7d2qookWgK\ni+JDQYlG4QwlGoUzNiLThNCL6Oj4iOZeQO8juw5TrSGyxB0EoFcqbBFG9RkrZRpjTAkKWF9dS/DW\n0TOu7vnoQGSmiyGbBWYQOHbyTCzMmeGgpasLkXHiGc9hG8BGjVXbTlWeZw39FHf3WeXe3ROZzLPy\nmRJonJFA3tbaKicyX0DEwRanSEfr+1shIvSmUThDiUbhjI2wpxVUaepD0UNjjNnfl4CqqdWttd2V\nta9fS3Wqo6M+rUsuhLG9OWc19ehUrMBfAxvDCg/GGLMNwVu1JscPX95I/LBXEWfgk6c2K5Sr//0l\nd+MNKsIK4pRZ1y30h0ghL6kfc3WMg4G8j6MDNk8EnrCrJJE90oSdkOtELPBY3NsYYxZWAcz7oDeN\nwhlKNApnKNEonLERmeb8XHKeKz2Waep18ULfjLm/5WAgsoUHgUWzBcs+O49EXX7zjtsXr8AbvA9q\n9cs/fkPr3seiLu8+soLOb2Xu8y++LsYHO/wsh49Fjd9JOfB7MsOizzRFAVRlCLwaXrKb4p//5d+L\n8fc+fkJzJ9DuudeGQC6rQna9jE0/rJIqrYeRg940Cmco0Sic4eV2vzqF4n+B3jQKZyjRKJyhRKNw\nhhKNwhlKNApnKNEonKFEo3CGEo3CGUo0Cmco0SicoUSjcIYSjcIZSjQKZyjRKJyhRKNwhhKNwhlK\nNApnKNEonKFEo3CGEo3CGUo0Cmco0SicoUSjcMafAQiqEO+JiqM6AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7feff312ae80>"
      ]
     },
     "metadata": {
      "tags": []
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Get a sample\n",
    "sample = split_df[split_df.split==\"test\"].iloc[0]\n",
    "plt.imshow(sample.image)\n",
    "plt.axis(\"off\")\n",
    "print (\"Actual:\", sample.category)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 187
    },
    "colab_type": "code",
    "id": "cp-rN2eeybae",
    "outputId": "a88e448d-63f5-422c-ef72-b0d724b7cdd9"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'category': 'bird', 'probability': 0.9989404},\n",
       " {'category': 'cat', 'probability': 0.0007951584},\n",
       " {'category': 'frog', 'probability': 6.979216e-05},\n",
       " {'category': 'deer', 'probability': 6.717988e-05},\n",
       " {'category': 'dog', 'probability': 6.0696475e-05},\n",
       " {'category': 'plane', 'probability': 5.353437e-05},\n",
       " {'category': 'horse', 'probability': 1.30456265e-05},\n",
       " {'category': 'ship', 'probability': 7.519804e-08},\n",
       " {'category': 'car', 'probability': 1.6445002e-08},\n",
       " {'category': 'truck', 'probability': 1.5949132e-09}]"
      ]
     },
     "execution_count": 78,
     "metadata": {
      "tags": []
     },
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Inference\n",
    "category = list(vectorizer.category_vocab.token_to_idx.keys())[0] # random filler category\n",
    "infer_df = pd.DataFrame([[sample.image, category, \"infer\"]], columns=['image', 'category', 'split'])\n",
    "infer_dataset = ImageDataset(df=infer_df, vectorizer=vectorizer, infer=True)\n",
    "results = inference.predict_category(dataset=infer_dataset)\n",
    "results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1YHneO3SStOp"
   },
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "gGHaKTe1SuEk"
   },
   "source": [
    "- segmentation\n",
    "- interpretability via activation maps\n",
    "- processing images of different sizes"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "collapsed_sections": [
    "W_2LVROYwFyL",
    "muXdsetFx4QW",
    "p2SLUqQp6S4-",
    "cOH5OuG07Ohv",
    "gbIKSQ5a7jsG",
    "DyfX-utj8JTz",
    "TtnGYqxk9Hxc",
    "OdZfoEKc-BBm",
    "kCkVhv54G6Ui",
    "5rz1g7FrOlQg",
    "KFEGIgA1NKJ-"
   ],
   "name": "15_Computer_Vision",
   "provenance": [],
   "toc_visible": true,
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
